From be0c84faad8fbcac7ad863afbe424f1dc2a89fab Mon Sep 17 00:00:00 2001 From: Sabine Date: Tue, 15 Jul 2025 15:09:10 +0200 Subject: [PATCH 001/140] chore(common): use new namespace km_vk for constants(2.) --- common/include/keyman_vkey.h | 18 ++++++++++++++++++ core/src/kmx/kmx_actions.cpp | 1 + core/src/kmx/kmx_base.h | 1 + core/src/kmx/kmx_capslock.cpp | 1 + core/src/kmx/kmx_context.cpp | 1 + core/src/kmx/kmx_conversion.cpp | 1 + core/src/kmx/kmx_conversion.h | 4 ++-- core/src/kmx/kmx_debugger.cpp | 1 + core/src/kmx/kmx_environment.cpp | 1 + core/src/kmx/kmx_file.cpp | 1 + core/src/kmx/kmx_modifiers.cpp | 1 + core/src/kmx/kmx_options.cpp | 1 + core/src/kmx/kmx_processevent.cpp | 1 + core/src/kmx/kmx_processor.cpp | 1 + core/src/kmx/kmx_xstring.cpp | 1 + core/tests/unit/kmnkbd/debug_api.tests.cpp | 1 + core/tests/unit/kmnkbd/debug_items.hpp | 1 + core/tests/unit/kmnkbd/kmx_context.tests.cpp | 1 + core/tests/unit/kmnkbd/kmx_xstring.tests.cpp | 1 + .../unit/kmx/kmx_external_event.tests.cpp | 1 + core/tests/unit/kmx/kmx_imx.tests.cpp | 1 + core/tests/unit/kmx/kmx_key_list.tests.cpp | 1 + core/tests/unit/ldml/kmx_plus.tests.cpp | 1 + core/tests/unit/ldml/transforms.tests.cpp | 1 + 24 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 common/include/keyman_vkey.h diff --git a/common/include/keyman_vkey.h b/common/include/keyman_vkey.h new file mode 100644 index 0000000000..c56edad6b1 --- /dev/null +++ b/common/include/keyman_vkey.h @@ -0,0 +1,18 @@ + +namespace km_vk { + +#define VK_SPACE 0x20 +#define VK_COLON 0xBA +#define VK_EQUAL 0xBB +#define VK_COMMA 0xBC +#define VK_HYPHEN 0xBD +#define VK_PERIOD 0xBE +#define VK_SLASH 0xBF +#define VK_ACCENT 0xC0 +#define VK_LBRKT 0xDB +#define VK_BKSLASH 0xDC +#define VK_RBRKT 0xDD +#define VK_QUOTE 0xDE +#define VK_xDF 0xDF + +} // namespace km_vk \ No newline at end of file diff --git a/core/src/kmx/kmx_actions.cpp b/core/src/kmx/kmx_actions.cpp index 5ea5f4e8a1..62d3651ed0 100644 --- a/core/src/kmx/kmx_actions.cpp +++ b/core/src/kmx/kmx_actions.cpp @@ -6,6 +6,7 @@ using namespace km::core; using namespace kmx; +using namespace km_vk; void KMX_Actions::ResetQueue() { diff --git a/core/src/kmx/kmx_base.h b/core/src/kmx/kmx_base.h index e798ff56b7..c1353540c4 100644 --- a/core/src/kmx/kmx_base.h +++ b/core/src/kmx/kmx_base.h @@ -1,6 +1,7 @@ #pragma once #include "keyman_core.h" +#include "../../../common/include/keyman_vkey.h" #if defined(_WIN32) || defined(_WIN64) #define snprintf _snprintf diff --git a/core/src/kmx/kmx_capslock.cpp b/core/src/kmx/kmx_capslock.cpp index c304f4ddd3..8c02a7559d 100644 --- a/core/src/kmx/kmx_capslock.cpp +++ b/core/src/kmx/kmx_capslock.cpp @@ -7,6 +7,7 @@ using namespace km::core; using namespace kmx; +using namespace km_vk; /** * Set caps lock state. diff --git a/core/src/kmx/kmx_context.cpp b/core/src/kmx/kmx_context.cpp index 2a0a0ab614..1b573198cc 100644 --- a/core/src/kmx/kmx_context.cpp +++ b/core/src/kmx/kmx_context.cpp @@ -6,6 +6,7 @@ using namespace km::core; using namespace kmx; +using namespace km_vk; /* KMX_Context */ diff --git a/core/src/kmx/kmx_conversion.cpp b/core/src/kmx/kmx_conversion.cpp index a3367b0414..c50091daf5 100644 --- a/core/src/kmx/kmx_conversion.cpp +++ b/core/src/kmx/kmx_conversion.cpp @@ -5,6 +5,7 @@ using namespace km::core; using namespace kmx; +using namespace km_vk; /* This array is lifted from preservedkeymap.cpp */ diff --git a/core/src/kmx/kmx_conversion.h b/core/src/kmx/kmx_conversion.h index 803033e100..66f8ff31d6 100644 --- a/core/src/kmx/kmx_conversion.h +++ b/core/src/kmx/kmx_conversion.h @@ -6,7 +6,7 @@ namespace km { namespace core { namespace kmx { -#define VK_SPACE 0x20 +/*#define VK_SPACE 0x20 #define VK_COLON 0xBA #define VK_EQUAL 0xBB #define VK_COMMA 0xBC @@ -18,7 +18,7 @@ namespace kmx { #define VK_BKSLASH 0xDC #define VK_RBRKT 0xDD #define VK_QUOTE 0xDE -#define VK_xDF 0xDF +#define VK_xDF 0xDF*/ /** * This maps an ASCII character corresponding to a US EN key cap to a diff --git a/core/src/kmx/kmx_debugger.cpp b/core/src/kmx/kmx_debugger.cpp index f380e28942..22a7281cd6 100644 --- a/core/src/kmx/kmx_debugger.cpp +++ b/core/src/kmx/kmx_debugger.cpp @@ -10,6 +10,7 @@ using namespace km::core; using namespace kmx; +using namespace km_vk; void KMX_DebugItems::push_item( uint8_t type, diff --git a/core/src/kmx/kmx_environment.cpp b/core/src/kmx/kmx_environment.cpp index bd4d57d0e7..d01c925ac7 100644 --- a/core/src/kmx/kmx_environment.cpp +++ b/core/src/kmx/kmx_environment.cpp @@ -9,6 +9,7 @@ using namespace km::core; using namespace kmx; +using namespace km_vk; namespace { km_core_cu const diff --git a/core/src/kmx/kmx_file.cpp b/core/src/kmx/kmx_file.cpp index 2eea018581..2b6dfa2fd8 100644 --- a/core/src/kmx/kmx_file.cpp +++ b/core/src/kmx/kmx_file.cpp @@ -8,6 +8,7 @@ using namespace km::core; using namespace kmx; +using namespace km_vk; #if defined(_WIN32) || defined(_WIN64) #include diff --git a/core/src/kmx/kmx_modifiers.cpp b/core/src/kmx/kmx_modifiers.cpp index a0173c9cdc..a7f9301c2d 100644 --- a/core/src/kmx/kmx_modifiers.cpp +++ b/core/src/kmx/kmx_modifiers.cpp @@ -6,6 +6,7 @@ using namespace km::core; using namespace kmx; +using namespace km_vk; #define MAX_RSHIFT 24 #define MAX_KSHIFT 18 diff --git a/core/src/kmx/kmx_options.cpp b/core/src/kmx/kmx_options.cpp index 6d844d1874..4a4a4dd221 100644 --- a/core/src/kmx/kmx_options.cpp +++ b/core/src/kmx/kmx_options.cpp @@ -8,6 +8,7 @@ using namespace km::core; using namespace kmx; +using namespace km_vk; int KMX_Options::_GetIndex(std::u16string const &key) const { auto i = 0U; diff --git a/core/src/kmx/kmx_processevent.cpp b/core/src/kmx/kmx_processevent.cpp index 921e8d9212..ee93e2edc2 100644 --- a/core/src/kmx/kmx_processevent.cpp +++ b/core/src/kmx/kmx_processevent.cpp @@ -8,6 +8,7 @@ using namespace km::core; using namespace kmx; +using namespace km_vk; /* Globals */ diff --git a/core/src/kmx/kmx_processor.cpp b/core/src/kmx/kmx_processor.cpp index 7ce8fad6ba..223de78faf 100644 --- a/core/src/kmx/kmx_processor.cpp +++ b/core/src/kmx/kmx_processor.cpp @@ -5,6 +5,7 @@ using namespace km::core; using namespace kmx; +using namespace km_vk; static KMX_BOOL ContextItemsFromAppContext(KMX_WCHAR *buf, km_core_context_item** outPtr) diff --git a/core/src/kmx/kmx_xstring.cpp b/core/src/kmx/kmx_xstring.cpp index 794ba26889..2a5c83f683 100644 --- a/core/src/kmx/kmx_xstring.cpp +++ b/core/src/kmx/kmx_xstring.cpp @@ -10,6 +10,7 @@ using namespace km::core; using namespace kmx; +using namespace km_vk; const km_core_cu *km::core::kmx::u16chr(const km_core_cu *p, km_core_cu ch) { while (*p) { diff --git a/core/tests/unit/kmnkbd/debug_api.tests.cpp b/core/tests/unit/kmnkbd/debug_api.tests.cpp index d40664436b..47355c5096 100644 --- a/core/tests/unit/kmnkbd/debug_api.tests.cpp +++ b/core/tests/unit/kmnkbd/debug_api.tests.cpp @@ -25,6 +25,7 @@ #include "../load_kmx_file.hpp" using namespace km::core::kmx; +using namespace km_vk; km_core_option_item test_env_opts[] = { diff --git a/core/tests/unit/kmnkbd/debug_items.hpp b/core/tests/unit/kmnkbd/debug_items.hpp index 9e281631f6..62e519e40a 100644 --- a/core/tests/unit/kmnkbd/debug_items.hpp +++ b/core/tests/unit/kmnkbd/debug_items.hpp @@ -6,6 +6,7 @@ #include using namespace km::core::kmx; +using namespace km_vk; namespace { diff --git a/core/tests/unit/kmnkbd/kmx_context.tests.cpp b/core/tests/unit/kmnkbd/kmx_context.tests.cpp index 0679c34d1a..ef37378c85 100644 --- a/core/tests/unit/kmnkbd/kmx_context.tests.cpp +++ b/core/tests/unit/kmnkbd/kmx_context.tests.cpp @@ -16,6 +16,7 @@ #include using namespace km::core::kmx; +using namespace km_vk; using namespace std; void diff --git a/core/tests/unit/kmnkbd/kmx_xstring.tests.cpp b/core/tests/unit/kmnkbd/kmx_xstring.tests.cpp index 529e230810..672fbd667e 100644 --- a/core/tests/unit/kmnkbd/kmx_xstring.tests.cpp +++ b/core/tests/unit/kmnkbd/kmx_xstring.tests.cpp @@ -22,6 +22,7 @@ #include using namespace km::core::kmx; +using namespace km_vk; using namespace std; #define U_1F609_WINKING_FACE u"\U0001F609" diff --git a/core/tests/unit/kmx/kmx_external_event.tests.cpp b/core/tests/unit/kmx/kmx_external_event.tests.cpp index 15f2d284df..bedcb899de 100644 --- a/core/tests/unit/kmx/kmx_external_event.tests.cpp +++ b/core/tests/unit/kmx/kmx_external_event.tests.cpp @@ -26,6 +26,7 @@ */ using namespace km::core::kmx; +using namespace km_vk; km_core_option_item test_env_opts[] = { diff --git a/core/tests/unit/kmx/kmx_imx.tests.cpp b/core/tests/unit/kmx/kmx_imx.tests.cpp index ab2bbb372b..b9c994b51a 100644 --- a/core/tests/unit/kmx/kmx_imx.tests.cpp +++ b/core/tests/unit/kmx/kmx_imx.tests.cpp @@ -27,6 +27,7 @@ */ using namespace km::core::kmx; +using namespace km_vk; km_core_option_item test_env_opts[] = { diff --git a/core/tests/unit/kmx/kmx_key_list.tests.cpp b/core/tests/unit/kmx/kmx_key_list.tests.cpp index 7b811bd0b1..62da63d704 100644 --- a/core/tests/unit/kmx/kmx_key_list.tests.cpp +++ b/core/tests/unit/kmx/kmx_key_list.tests.cpp @@ -18,6 +18,7 @@ #include "../load_kmx_file.hpp" using namespace km::core::kmx; +using namespace km_vk; km_core_option_item test_env_opts[] = { diff --git a/core/tests/unit/ldml/kmx_plus.tests.cpp b/core/tests/unit/ldml/kmx_plus.tests.cpp index c0643a5d60..30143db7ef 100644 --- a/core/tests/unit/ldml/kmx_plus.tests.cpp +++ b/core/tests/unit/ldml/kmx_plus.tests.cpp @@ -11,6 +11,7 @@ #include "utfcodec.hpp" using namespace km::core::kmx; +using namespace km_vk; TEST(KMXPlusTest, test_COMP_KMXPLUS_KEYS_KEY) { COMP_KMXPLUS_KEYS_KEY e[2] = { diff --git a/core/tests/unit/ldml/transforms.tests.cpp b/core/tests/unit/ldml/transforms.tests.cpp index 411d941047..dd2aaa9291 100644 --- a/core/tests/unit/ldml/transforms.tests.cpp +++ b/core/tests/unit/ldml/transforms.tests.cpp @@ -42,6 +42,7 @@ using namespace km::core::ldml; using namespace km::core::kmx; +using namespace km_vk; void prepend_hex_oct(std::u32string &str, char32_t x) { From b4d76dcc5129e34ddf9dd16a8ff6fdbc43853473 Mon Sep 17 00:00:00 2001 From: Sabine Date: Tue, 15 Jul 2025 17:18:32 +0200 Subject: [PATCH 002/140] chore(common): use new namespace km_vk in charToKeyConversion and mac/mcompile/keymap.h --- common/include/keyman_vkey.h | 161 ++++++++++++++++-- .../src/kmcmplib/src/CharToKeyConversion.cpp | 5 +- linux/mcompile/keymap/keymap.h | 3 +- mac/mcompile/keymap.h | 8 +- 4 files changed, 158 insertions(+), 19 deletions(-) diff --git a/common/include/keyman_vkey.h b/common/include/keyman_vkey.h index c56edad6b1..fb02a2160b 100644 --- a/common/include/keyman_vkey.h +++ b/common/include/keyman_vkey.h @@ -1,18 +1,153 @@ +/* + * Keyman is copyright (C) SIL International. MIT License. + * Create Date: 16 Jul 2025 + * Author: Sabine Schmitt + * VKCodes for keyman + */ namespace km_vk { -#define VK_SPACE 0x20 -#define VK_COLON 0xBA -#define VK_EQUAL 0xBB -#define VK_COMMA 0xBC -#define VK_HYPHEN 0xBD -#define VK_PERIOD 0xBE -#define VK_SLASH 0xBF -#define VK_ACCENT 0xC0 -#define VK_LBRKT 0xDB -#define VK_BKSLASH 0xDC -#define VK_RBRKT 0xDD -#define VK_QUOTE 0xDE -#define VK_xDF 0xDF +#define VK_LBUTTON 0x01 +#define VK_RBUTTON 0x02 +#define VK_CANCEL 0x03 +#define VK_MBUTTON 0x04 + +#define VK_BACKSPACE 0x08 +#define VK_BACK 0x08 +#define VK_TAB 0x09 + +#define VK_CLEAR 0x0C +#define VK_RETURN 0x0D +#define VK_ENTER 0x0D + +#define VK_SHIFT 0x10 +#define VK_CONTROL 0x11 +#define VK_MENU 0x12 +#define VK_PAUSE 0x13 +#define VK_CAPITAL 0x14 +#define VK_KANA 0x15 +#define VK_HANGEUL 0x15 +#define VK_HANGUL 0x15 +#define VK_IME_ON 0x16 +#define VK_JUNJA 0x17 +#define VK_FINAL 0x18 +#define VK_HANJA 0x19 +#define VK_KANJI 0x19 +#define VK_IME_OFF 0x1A +#define VK_ESCAPE 0x1B +#define VK_CONVERT 0x1C +#define VK_NONCONVERT 0x1D +#define VK_ACCEPT 0x1E +#define VK_MODECHANGE 0x1F +#define VK_SPACE 0x20 +#define VK_PRIOR 0x21 +#define VK_NEXT 0x22 +#define VK_END 0x23 +#define VK_HOME 0x24 +#define VK_LEFT 0x25 +#define VK_UP 0x26 +#define VK_RIGHT 0x27 +#define VK_DOWN 0x28 +#define VK_SELECT 0x29 +#define VK_PRINT 0x2A +#define VK_EXECUTE 0x2B +#define VK_SNAPSHOT 0x2C +#define VK_INSERT 0x2D +#define VK_DECIMAL 0x2E + +#define VK_KEY_0 0x30 +#define VK_KEY_1 0x31 +#define VK_KEY_2 0x32 +#define VK_KEY_3 0x33 +#define VK_KEY_4 0x34 +#define VK_KEY_5 0x35 +#define VK_KEY_6 0x36 +#define VK_KEY_7 0x37 +#define VK_KEY_8 0x38 +#define VK_KEY_9 0x39 + +#define VK_KEY_A 0x41 +#define VK_KEY_B 0x42 +#define VK_KEY_C 0x43 +#define VK_KEY_D 0x44 +#define VK_KEY_E 0x45 +#define VK_KEY_F 0x46 +#define VK_KEY_G 0x47 +#define VK_KEY_H 0x48 +#define VK_KEY_I 0x49 +#define VK_KEY_J 0x4A +#define VK_KEY_K 0x4B +#define VK_KEY_L 0x4C +#define VK_KEY_M 0x4D +#define VK_KEY_N 0x4E +#define VK_KEY_O 0x4F +#define VK_KEY_P 0x50 +#define VK_KEY_Q 0x51 +#define VK_KEY_R 0x52 +#define VK_KEY_S 0x53 +#define VK_KEY_T 0x54 +#define VK_KEY_U 0x55 +#define VK_KEY_V 0x56 +#define VK_KEY_W 0x57 +#define VK_KEY_X 0x58 +#define VK_KEY_Y 0x59 +#define VK_KEY_Z 0x5A + +#define VK_NUMPAD0 0x60 +#define VK_NUMPAD1 0x61 +#define VK_NUMPAD2 0x62 +#define VK_NUMPAD3 0x63 +#define VK_NUMPAD4 0x64 +#define VK_NUMPAD5 0x65 +#define VK_NUMPAD6 0x66 +#define VK_NUMPAD7 0x67 +#define VK_NUMPAD8 0x68 +#define VK_NUMPAD9 0x69 +#define VK_NUMPAD_MULTIPLY 0x6A +#define VK_NUMPAD_ADD 0x6B + +#define VK_NUMPAD_SUBTRACT 0x6D +#define VK_NUMPAD_DECIMAL 0x6E +#define VK_NUMPAD_DIVIDE 0x6F +#define VK_DIVIDE 0x6F + +#define VK_NUMLOCK 0x90 + +#define VK_LSHIFT 0xA0 +#define VK_RSHIFT 0xA1 +#define VK_LCONTROL 0xA2 +#define VK_RCONTROL 0xA3 +#define VK_LMENU 0xA4 +#define VK_RMENU 0xA5 + +#define VK_OEM_1 0xBA +#define VK_COLON 0xBA +#define VK_SEMICOLON 0xBA +#define VK_OEM_PLUS 0xBB +#define VK_EQUAL 0xBB +#define VK_OEM_COMMA 0xBC +#define VK_COMMA 0xBC +#define VK_OEM_MINUS 0xBD +#define VK_HYPHEN 0xBD +#define VK_MINUS 0xBD +#define VK_OEM_PERIOD 0xBE +#define VK_PERIOD 0xBE +#define VK_OEM_2 0xBF +#define VK_SLASH 0xBF +#define VK_OEM_3 0xC0 +#define VK_ACCENT 0xC0 +#define VK_GRAVE 0xC0 + +#define VK_LEFT_BRACKET 0xDB +#define VK_LBRKT 0xDB +#define VK_BACKSLASH 0xDC +#define VK_BKSLASH 0xDC +#define VK_RIGHT_BRACKET 0xDD +#define VK_RBRKT 0xDD +#define VK_QUOTE 0xDE +#define VK_xDF 0xDF +#define VK_OEM_102 0xE2 + +#define VK_OEM_CLEAR 0xFE } // namespace km_vk \ No newline at end of file diff --git a/developer/src/kmcmplib/src/CharToKeyConversion.cpp b/developer/src/kmcmplib/src/CharToKeyConversion.cpp index 0c2b652d09..b25a813bb0 100644 --- a/developer/src/kmcmplib/src/CharToKeyConversion.cpp +++ b/developer/src/kmcmplib/src/CharToKeyConversion.cpp @@ -1,10 +1,11 @@ #include "pch.h" #include "CharToKeyConversion.h" +#include "../../../../common/include/keyman_vkey.h" /* Following VKeys come from Windows */ -#define VK_COLON 0xBA +/*#define VK_COLON 0xBA #define VK_EQUAL 0xBB #define VK_COMMA 0xBC #define VK_HYPHEN 0xBD @@ -19,7 +20,7 @@ #define VK_NUMPAD0 0x60 #define VK_NUMPAD9 0x69 -#define VK_SPACE 0x20 +#define VK_SPACE 0x20*/ /* Following code lifted from syskbd.cpp and tweaked for compiler use. Todo: consolidate */ diff --git a/linux/mcompile/keymap/keymap.h b/linux/mcompile/keymap/keymap.h index 74e67352b0..0e10e1d1e6 100644 --- a/linux/mcompile/keymap/keymap.h +++ b/linux/mcompile/keymap/keymap.h @@ -16,6 +16,7 @@ #include "km_u16.h" #include + enum ShiftState { Base = 0, // 0 Shft = 1, // 1 @@ -45,7 +46,7 @@ enum ShiftState { #define VK_OEM_102 0xE2 // "<>" or "\|" on RT 102-key kbd. #define VK_DIVIDE 0x6F -#define VK_CANCEL 3 +#define VK_CANCEL 0x03 #define VK_DECIMAL 0x2E #define VK_OEM_CLEAR 0xFE diff --git a/mac/mcompile/keymap.h b/mac/mcompile/keymap.h index 99e563b4c0..9eb98a0325 100644 --- a/mac/mcompile/keymap.h +++ b/mac/mcompile/keymap.h @@ -9,6 +9,8 @@ #include "../../common/include/km_u16.h" +#include "../../common/include/keyman_vkey.h" +//#include "../../../../common/include/keyman_vkey.h" enum ShiftState { Base = 0, // 0 Shft = 1, // 1 @@ -22,7 +24,7 @@ enum ShiftState { ShftXxxx = Shft | Xxxx, // 9 }; -#define VK_SPACE 0x20 +/*#define VK_SPACE 0x20 #define VK_COLON 0xBA #define VK_EQUAL 0xBB #define VK_COMMA 0xBC @@ -38,7 +40,7 @@ enum ShiftState { #define VK_OEM_102 0xE2 // "<>" or "\|" on RT 102-key kbd. #define VK_DIVIDE 0x6F -#define VK_CANCEL 3 +#define VK_CANCEL 0x03 #define VK_DECIMAL 0x2E #define VK_OEM_CLEAR 0xFE @@ -53,7 +55,7 @@ enum ShiftState { #define VK_CONTROL 0x11 #define VK_MENU 0x12 #define VK_PAUSE 0x13 -#define VK_CAPITAL 0x14 +#define VK_CAPITAL 0x14*/ typedef std::vector vec_string_1D; typedef std::vector vec_dword_1D; From 0a43883a51a0c0bbb88d005952d3e2dcdc34500a Mon Sep 17 00:00:00 2001 From: Sabine Date: Tue, 15 Jul 2025 18:00:31 +0200 Subject: [PATCH 003/140] chore(common): use new namespace km_vk in linux/mcompile/keymap/keymap.h --- linux/mcompile/keymap/keymap.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/linux/mcompile/keymap/keymap.h b/linux/mcompile/keymap/keymap.h index 0e10e1d1e6..46b5b2a31f 100644 --- a/linux/mcompile/keymap/keymap.h +++ b/linux/mcompile/keymap/keymap.h @@ -15,6 +15,7 @@ #include #include "km_u16.h" #include +#include "../../../common/include/keyman_vkey.h" enum ShiftState { @@ -30,7 +31,7 @@ enum ShiftState { ShftXxxx = Shft | Xxxx, // 9 }; -#define VK_SPACE 0x20 +/*#define VK_SPACE 0x20 #define VK_COLON 0xBA #define VK_EQUAL 0xBB #define VK_COMMA 0xBC @@ -61,7 +62,7 @@ enum ShiftState { #define VK_CONTROL 0x11 #define VK_MENU 0x12 #define VK_PAUSE 0x13 -#define VK_CAPITAL 0x14 +#define VK_CAPITAL 0x14*/ typedef std::vector vec_string_1D; typedef std::vector vec_dword_1D; From 49d5270e296d6948c19bfa67120800ac7975d488 Mon Sep 17 00:00:00 2001 From: Sabine Date: Wed, 16 Jul 2025 10:56:41 +0200 Subject: [PATCH 004/140] chore(common): use new namespace km_vk in kmx_processevent.cpp, Compiler.cpp and savekeyboard.cpp --- common/include/keyman_vkey.h | 6 ++++++ core/src/kmx/kmx_processevent.h | 1 + developer/src/kmcmplib/src/Compiler.cpp | 2 ++ developer/src/kmdecomp/savekeyboard.cpp | 4 ++++ 4 files changed, 13 insertions(+) diff --git a/common/include/keyman_vkey.h b/common/include/keyman_vkey.h index fb02a2160b..5c880fcce5 100644 --- a/common/include/keyman_vkey.h +++ b/common/include/keyman_vkey.h @@ -150,4 +150,10 @@ namespace km_vk { #define VK_OEM_CLEAR 0xFE +/* + * VK__MAX _S2 defines the highest virtual key code defined in the system = 0xFF. Custom VK codes start at 256 + */ +#define VK__MAX 255 + + } // namespace km_vk \ No newline at end of file diff --git a/core/src/kmx/kmx_processevent.h b/core/src/kmx/kmx_processevent.h index 8082c37833..af42872e37 100644 --- a/core/src/kmx/kmx_processevent.h +++ b/core/src/kmx/kmx_processevent.h @@ -23,6 +23,7 @@ #include "kmx_options.h" #include "kmx_environment.h" #include "kmx_debugger.h" +#include "../../../common/include/keyman_vkey.h" /***************************************************************************/ diff --git a/developer/src/kmcmplib/src/Compiler.cpp b/developer/src/kmcmplib/src/Compiler.cpp index 3b53453dce..34deae8984 100644 --- a/developer/src/kmcmplib/src/Compiler.cpp +++ b/developer/src/kmcmplib/src/Compiler.cpp @@ -82,6 +82,7 @@ #include "../../../../common/windows/cpp/include/keymanversion.h" #include "../../../../common/windows/cpp/include/crc32.h" #include "../../../../common/windows/cpp/include/ConvertUTF.h" +#include "../../../../common/include/keyman_vkey.h" #include "debugstore.h" #include "NamedCodeConstants.h" @@ -110,6 +111,7 @@ #define SUBLANGID(lgid) ((uint16_t)(lgid) >> 10) using namespace kmcmp; +using namespace km_vk; KMX_BOOL AWarnDeprecatedCode_GLOBAL_LIB; diff --git a/developer/src/kmdecomp/savekeyboard.cpp b/developer/src/kmdecomp/savekeyboard.cpp index e71da93dbd..3d29ae3cfb 100644 --- a/developer/src/kmdecomp/savekeyboard.cpp +++ b/developer/src/kmdecomp/savekeyboard.cpp @@ -31,6 +31,10 @@ #include "../../../common/windows/cpp/include/legacy_kmx_file.h" #include "../../../common/windows/cpp/include/legacy_kmx_memory.h" #include "../../../common/windows/cpp/include/vkeys.h" +#include "../../../common/include/keyman_vkey.h" + +using namespace km_vk + #define SSN__PREFIX L"&" From a1465c0767ee9dc7215f6874562bbb5efd989d67 Mon Sep 17 00:00:00 2001 From: Sabine Date: Wed, 16 Jul 2025 11:34:09 +0200 Subject: [PATCH 005/140] chore(common): missing semicolon in savekeyboard.cpp --- common/include/keyman_vkey.h | 7 +++++-- developer/src/kmdecomp/savekeyboard.cpp | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/common/include/keyman_vkey.h b/common/include/keyman_vkey.h index 5c880fcce5..c4b9babe80 100644 --- a/common/include/keyman_vkey.h +++ b/common/include/keyman_vkey.h @@ -137,17 +137,20 @@ namespace km_vk { #define VK_OEM_3 0xC0 #define VK_ACCENT 0xC0 #define VK_GRAVE 0xC0 - #define VK_LEFT_BRACKET 0xDB #define VK_LBRKT 0xDB +#define VK_OEM_4 0xDB #define VK_BACKSLASH 0xDC #define VK_BKSLASH 0xDC +#define VK_OEM_5 0xDC #define VK_RIGHT_BRACKET 0xDD #define VK_RBRKT 0xDD +#define VK_OEM_6 0xDD #define VK_QUOTE 0xDE +#define VK_OEM_7 0xDE #define VK_xDF 0xDF +#define VK_OEM_8 0xDF #define VK_OEM_102 0xE2 - #define VK_OEM_CLEAR 0xFE /* diff --git a/developer/src/kmdecomp/savekeyboard.cpp b/developer/src/kmdecomp/savekeyboard.cpp index 3d29ae3cfb..4764371edd 100644 --- a/developer/src/kmdecomp/savekeyboard.cpp +++ b/developer/src/kmdecomp/savekeyboard.cpp @@ -33,7 +33,7 @@ #include "../../../common/windows/cpp/include/vkeys.h" #include "../../../common/include/keyman_vkey.h" -using namespace km_vk +using namespace km_vk; #define SSN__PREFIX L"&" From ed8e24812e939a22d3f1d6a3f02cd2043065ff9c Mon Sep 17 00:00:00 2001 From: Sabine Date: Wed, 16 Jul 2025 12:21:02 +0200 Subject: [PATCH 006/140] chore(common): remove VK_DECIMAL from keyman_vkey.h --- common/include/keyman_vkey.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/include/keyman_vkey.h b/common/include/keyman_vkey.h index c4b9babe80..8bea0dbfc5 100644 --- a/common/include/keyman_vkey.h +++ b/common/include/keyman_vkey.h @@ -53,7 +53,7 @@ namespace km_vk { #define VK_EXECUTE 0x2B #define VK_SNAPSHOT 0x2C #define VK_INSERT 0x2D -#define VK_DECIMAL 0x2E +//#define VK_DECIMAL 0x2E #define VK_KEY_0 0x30 #define VK_KEY_1 0x31 From 9a8e0523f98bf5272424f166a211c34a61d8981f Mon Sep 17 00:00:00 2001 From: Sabine Date: Wed, 16 Jul 2025 13:02:14 +0200 Subject: [PATCH 007/140] chore(common): use new namespace km_vk in imsample.cpp --- common/include/keyman_vkey.h | 2 +- developer/src/samples/imsample/imsample.cpp | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/common/include/keyman_vkey.h b/common/include/keyman_vkey.h index 8bea0dbfc5..59ecbfa9cf 100644 --- a/common/include/keyman_vkey.h +++ b/common/include/keyman_vkey.h @@ -53,7 +53,7 @@ namespace km_vk { #define VK_EXECUTE 0x2B #define VK_SNAPSHOT 0x2C #define VK_INSERT 0x2D -//#define VK_DECIMAL 0x2E +//#define VK_DECIMAL 0x2E // _S2 if I use it here kmdecomp complains because of duplicate macro definition here and in WinUser.h #define VK_KEY_0 0x30 #define VK_KEY_1 0x31 diff --git a/developer/src/samples/imsample/imsample.cpp b/developer/src/samples/imsample/imsample.cpp index f1993c53df..3e299fda7c 100644 --- a/developer/src/samples/imsample/imsample.cpp +++ b/developer/src/samples/imsample/imsample.cpp @@ -21,6 +21,9 @@ #include #include #include "imlib.h" +#include "../../../../common/include/keyman_vkey.h" + +using namespace km_vk; HWND hwnd; HWND hwndChild; From e2ed50293bb49bf4fd8e9b41de7c76fb64fd0519 Mon Sep 17 00:00:00 2001 From: Sabine Date: Thu, 17 Jul 2025 11:27:49 +0200 Subject: [PATCH 008/140] chore(common): use new namespace km_vk in kmdecomp --- common/include/keyman_vkey.h | 2 +- developer/src/kmdecomp/kmdecomp.cpp | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/common/include/keyman_vkey.h b/common/include/keyman_vkey.h index 59ecbfa9cf..44bb9441e1 100644 --- a/common/include/keyman_vkey.h +++ b/common/include/keyman_vkey.h @@ -53,7 +53,7 @@ namespace km_vk { #define VK_EXECUTE 0x2B #define VK_SNAPSHOT 0x2C #define VK_INSERT 0x2D -//#define VK_DECIMAL 0x2E // _S2 if I use it here kmdecomp complains because of duplicate macro definition here and in WinUser.h +#define VK_DECIMAL 0x2E // _S2 if I use it here kmdecomp complains because of duplicate macro definition here and in WinUser.h #define VK_KEY_0 0x30 #define VK_KEY_1 0x31 diff --git a/developer/src/kmdecomp/kmdecomp.cpp b/developer/src/kmdecomp/kmdecomp.cpp index c6a085fe2e..3dd3828a12 100644 --- a/developer/src/kmdecomp/kmdecomp.cpp +++ b/developer/src/kmdecomp/kmdecomp.cpp @@ -24,6 +24,10 @@ #include "../../../common/windows/cpp/include/keymanversion.h" #include "../../../common/windows/cpp/include/legacy_kmx_memory.h" #include "../../../common/windows/cpp/include/legacy_kmx_file.h" +#include "../../../common/include/keyman_vkey.h" + +using namespace km_vk; + BOOL LoadKeyboard(LPSTR fileName, LPKEYBOARD *lpKeyboard, LPBYTE *lpBitmap, DWORD *cbBitmap); From a0a3305d232a41358d772ba7841a420ae69d7441 Mon Sep 17 00:00:00 2001 From: Sabine Date: Thu, 17 Jul 2025 12:16:24 +0200 Subject: [PATCH 009/140] chore(common): include new file in kmdecomp/pch.h --- developer/src/kmanalyze/kmanalyze.cpp | 7 +++++-- developer/src/kmdecomp/pch.h | 2 ++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/developer/src/kmanalyze/kmanalyze.cpp b/developer/src/kmanalyze/kmanalyze.cpp index 579f85e0de..3b4532240f 100644 --- a/developer/src/kmanalyze/kmanalyze.cpp +++ b/developer/src/kmanalyze/kmanalyze.cpp @@ -7,6 +7,9 @@ #include #include #include +#include "../../../common/include/keyman_vkey.h" + +using namespace km_vk; BOOL LoadKeyboard(LPSTR fileName, LPKEYBOARD *lpKeyboard); @@ -602,7 +605,7 @@ void PrintTree(LPKEYBOARD kbd, GROUPTREE *t, int depth) { // MapVirtualKeys is copied from syskbd.cpp (keyman32) -#define VK_COLON 0xBA +/*#define VK_COLON 0xBA #define VK_EQUAL 0xBB #define VK_COMMA 0xBC #define VK_HYPHEN 0xBD @@ -613,7 +616,7 @@ void PrintTree(LPKEYBOARD kbd, GROUPTREE *t, int depth) { #define VK_BKSLASH 0xDC #define VK_RBRKT 0xDD #define VK_QUOTE 0xDE -#define VK_xDF 0xDF +#define VK_xDF 0xDF*/ WCHAR MapVirtualKeys(WORD keyCode, UINT shiftFlags) { diff --git a/developer/src/kmdecomp/pch.h b/developer/src/kmdecomp/pch.h index 9457eb85fb..bc43dfb9ff 100644 --- a/developer/src/kmdecomp/pch.h +++ b/developer/src/kmdecomp/pch.h @@ -2,3 +2,5 @@ #define STRICT #include + +#include "../../../common/include/keyman_vkey.h" From c04ef63db632f679033f23227a91eaeaf6874f1a Mon Sep 17 00:00:00 2001 From: Sabine Date: Thu, 17 Jul 2025 12:16:24 +0200 Subject: [PATCH 010/140] chore(common): undo changes for kmdecomp --- developer/src/kmanalyze/kmanalyze.cpp | 7 +++++-- developer/src/kmdecomp/kmdecomp.cpp | 4 ---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/developer/src/kmanalyze/kmanalyze.cpp b/developer/src/kmanalyze/kmanalyze.cpp index 579f85e0de..3b4532240f 100644 --- a/developer/src/kmanalyze/kmanalyze.cpp +++ b/developer/src/kmanalyze/kmanalyze.cpp @@ -7,6 +7,9 @@ #include #include #include +#include "../../../common/include/keyman_vkey.h" + +using namespace km_vk; BOOL LoadKeyboard(LPSTR fileName, LPKEYBOARD *lpKeyboard); @@ -602,7 +605,7 @@ void PrintTree(LPKEYBOARD kbd, GROUPTREE *t, int depth) { // MapVirtualKeys is copied from syskbd.cpp (keyman32) -#define VK_COLON 0xBA +/*#define VK_COLON 0xBA #define VK_EQUAL 0xBB #define VK_COMMA 0xBC #define VK_HYPHEN 0xBD @@ -613,7 +616,7 @@ void PrintTree(LPKEYBOARD kbd, GROUPTREE *t, int depth) { #define VK_BKSLASH 0xDC #define VK_RBRKT 0xDD #define VK_QUOTE 0xDE -#define VK_xDF 0xDF +#define VK_xDF 0xDF*/ WCHAR MapVirtualKeys(WORD keyCode, UINT shiftFlags) { diff --git a/developer/src/kmdecomp/kmdecomp.cpp b/developer/src/kmdecomp/kmdecomp.cpp index 3dd3828a12..c6a085fe2e 100644 --- a/developer/src/kmdecomp/kmdecomp.cpp +++ b/developer/src/kmdecomp/kmdecomp.cpp @@ -24,10 +24,6 @@ #include "../../../common/windows/cpp/include/keymanversion.h" #include "../../../common/windows/cpp/include/legacy_kmx_memory.h" #include "../../../common/windows/cpp/include/legacy_kmx_file.h" -#include "../../../common/include/keyman_vkey.h" - -using namespace km_vk; - BOOL LoadKeyboard(LPSTR fileName, LPKEYBOARD *lpKeyboard, LPBYTE *lpBitmap, DWORD *cbBitmap); From eb2045b631eaf42c52937ed6d6d540f1e620d19a Mon Sep 17 00:00:00 2001 From: Sabine Date: Thu, 17 Jul 2025 13:36:37 +0200 Subject: [PATCH 011/140] chore(common): merge branch undo --- developer/src/kmdecomp/pch.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/developer/src/kmdecomp/pch.h b/developer/src/kmdecomp/pch.h index bc43dfb9ff..9457eb85fb 100644 --- a/developer/src/kmdecomp/pch.h +++ b/developer/src/kmdecomp/pch.h @@ -2,5 +2,3 @@ #define STRICT #include - -#include "../../../common/include/keyman_vkey.h" From 50204abc3d6d73cbbccc21b926b36e48d0a77176 Mon Sep 17 00:00:00 2001 From: Sabine Date: Thu, 17 Jul 2025 14:54:56 +0200 Subject: [PATCH 012/140] chore(common): disable VK_DECIMAL again in keyman_vkeys.h --- common/include/keyman_vkey.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/include/keyman_vkey.h b/common/include/keyman_vkey.h index 44bb9441e1..59ecbfa9cf 100644 --- a/common/include/keyman_vkey.h +++ b/common/include/keyman_vkey.h @@ -53,7 +53,7 @@ namespace km_vk { #define VK_EXECUTE 0x2B #define VK_SNAPSHOT 0x2C #define VK_INSERT 0x2D -#define VK_DECIMAL 0x2E // _S2 if I use it here kmdecomp complains because of duplicate macro definition here and in WinUser.h +//#define VK_DECIMAL 0x2E // _S2 if I use it here kmdecomp complains because of duplicate macro definition here and in WinUser.h #define VK_KEY_0 0x30 #define VK_KEY_1 0x31 From b1575b47d5d18712dec427704c7de6b33d88a4f7 Mon Sep 17 00:00:00 2001 From: MMaynor Date: Mon, 16 Mar 2026 15:22:39 +0700 Subject: [PATCH 013/140] testing message --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 601fdcc696..84482913cf 100644 --- a/README.md +++ b/README.md @@ -33,3 +33,6 @@ Keyman is an open source project distributed under the [MIT license](LICENSE.md) * [Documentation](https://help.keyman.com/) * [Online Keyboard](https://keymanweb.com/) * [SIL Language Technology - software.sil.org](https://software.sil.org/) + + +# test \ No newline at end of file From b0399d3089c13afc7bbe72fd41d08f0125a2a82f Mon Sep 17 00:00:00 2001 From: kongchanlina Date: Mon, 16 Mar 2026 16:52:37 +0700 Subject: [PATCH 014/140] 90% of Dark Mode --- .../kMAPro/src/main/AndroidManifest.xml | 6 +- .../kmapro/GetStartedActivity.java | 7 +- .../kmapro/KeyboardSettingsActivity.java | 6 +- .../kmapro/KeymanSettingsInstallActivity.java | 7 +- .../kmapro/LanguageSettingsActivity.java | 6 +- .../kmapro/LanguagesSettingsActivity.java | 3 +- .../com/tavultesoft/kmapro/MainActivity.java | 52 ++++++++++----- .../kmapro/SelectLanguageFragment.java | 4 +- .../kmapro/SelectPackageActivity.java | 2 +- .../src/main/res/drawable-hdpi/keyman_ic.png | Bin 0 -> 1686 bytes .../main/res/drawable-hdpi/keyman_text.png | Bin 0 -> 2683 bytes .../src/main/res/drawable/branded_logo.xml | 4 +- .../res/drawable/branded_logo_dark_mode.xml | 25 +++++++ .../main/res/drawable/ic_action_decrement.xml | 3 +- .../res/drawable/ic_action_forward_mode.xml | 4 ++ .../drawable/ic_action_get_started_mode.xml | 4 ++ .../main/res/drawable/ic_action_increment.xml | 3 +- .../main/res/drawable/ic_action_info_mode.xml | 4 ++ .../res/drawable/ic_action_overflow_mode.xml | 4 ++ .../res/drawable/ic_action_share_mode.xml | 4 ++ .../res/drawable/ic_action_textsize_mode.xml | 4 ++ .../main/res/drawable/ic_action_timelapse.xml | 7 +- .../res/drawable/ic_action_trash_mode.xml | 4 ++ .../src/main/res/drawable/ic_check_mode.xml | 4 ++ .../src/main/res/drawable/ic_checkbox.xml | 30 +++++++++ .../res/drawable/ic_cloud_download_mode.xml | 6 ++ .../main/res/drawable/ic_content_add_mode.xml | 4 ++ .../main/res/drawable/ic_folder_open_mode.xml | 4 ++ .../main/res/drawable/ic_settings_mode.xml | 4 ++ .../main/res/drawable/keyman_logo_mode.xml | 20 ++++++ .../drawable/keyman_logo_splash_dark_mode.png | Bin 0 -> 8222 bytes .../src/main/res/layout/activity_info.xml | 6 +- .../res/layout/activity_select_language.xml | 1 + .../res/layout/fragment_select_language.xml | 1 + .../res/layout/get_started_list_layout.xml | 3 +- .../res/layout/get_started_row_layout.xml | 14 ++-- .../main/res/layout/get_started_toolbar.xml | 9 ++- .../res/layout/keyman_settings_layout.xml | 1 + .../src/main/res/layout/list_row_layout1.xml | 38 +++++++++++ .../res/layout/preference_add_icon_layout.xml | 4 +- .../preference_duration_icon_layout.xml | 4 +- .../layout/preference_file_browser_layout.xml | 4 +- .../layout/preference_height_icon_layout.xml | 5 +- .../res/layout/preference_icon_layout.xml | 3 +- .../preference_translate_icon_layout.xml | 4 +- .../main/res/layout/text_size_controller.xml | 9 ++- .../KMAPro/kMAPro/src/main/res/menu/main.xml | 30 ++++++--- .../src/main/res/values-night/colors.xml | 17 +++++ .../src/main/res/values-night/styles.xml | 62 ++++++++++++++++++ .../kMAPro/src/main/res/values/colors.xml | 12 +++- .../kMAPro/src/main/res/values/dimens.xml | 3 +- .../kMAPro/src/main/res/values/styles.xml | 26 ++++++-- 52 files changed, 411 insertions(+), 80 deletions(-) create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable-hdpi/keyman_ic.png create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable-hdpi/keyman_text.png create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable/branded_logo_dark_mode.xml create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable/ic_action_forward_mode.xml create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable/ic_action_get_started_mode.xml create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable/ic_action_info_mode.xml create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable/ic_action_overflow_mode.xml create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable/ic_action_share_mode.xml create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable/ic_action_textsize_mode.xml create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable/ic_action_trash_mode.xml create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable/ic_check_mode.xml create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable/ic_checkbox.xml create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable/ic_cloud_download_mode.xml create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable/ic_content_add_mode.xml create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable/ic_folder_open_mode.xml create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable/ic_settings_mode.xml create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable/keyman_logo_mode.xml create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable/keyman_logo_splash_dark_mode.png create mode 100644 android/KMAPro/kMAPro/src/main/res/layout/list_row_layout1.xml create mode 100644 android/KMAPro/kMAPro/src/main/res/values-night/colors.xml create mode 100644 android/KMAPro/kMAPro/src/main/res/values-night/styles.xml diff --git a/android/KMAPro/kMAPro/src/main/AndroidManifest.xml b/android/KMAPro/kMAPro/src/main/AndroidManifest.xml index bdb1d18a43..446405033e 100644 --- a/android/KMAPro/kMAPro/src/main/AndroidManifest.xml +++ b/android/KMAPro/kMAPro/src/main/AndroidManifest.xml @@ -241,7 +241,8 @@ android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize" android:label="@string/app_name" android:launchMode="singleTask" - android:theme="@style/AppTheme.Light.Dialog"/> + android:theme="@style/AppTheme.Dialog"/> + + android:theme="@style/AppTheme.Dialog" /> + kbList = KMManager.getKeyboardsList(this); diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeyboardSettingsActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeyboardSettingsActivity.java index 689548a56e..68b0a19fc0 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeyboardSettingsActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeyboardSettingsActivity.java @@ -96,7 +96,8 @@ public final class KeyboardSettingsActivity extends AppCompatActivity { // Display notification to download update if available if (kbd.hasUpdateAvailable()) { hashMap.put(subtitleKey, context.getString(R.string.update_available, kbVersion)); - icon = String.valueOf(R.drawable.ic_cloud_download); +// icon = String.valueOf(R.drawable.ic_cloud_download); + icon = String.valueOf(R.drawable.ic_cloud_download_mode); } hashMap.put(iconKey, icon); infoList.add(hashMap); @@ -105,7 +106,8 @@ public final class KeyboardSettingsActivity extends AppCompatActivity { hashMap = new HashMap<>(); final String customHelpLink = kbd.getHelpLink(); // Check if app declared FileProvider - icon = String.valueOf(R.drawable.ic_action_forward); + //icon = String.valueOf(R.drawable.ic_action_forward); + icon = String.valueOf(R.drawable.ic_action_forward_mode); // Don't show help link arrow if File Provider unavailable, or custom help doesn't exist if ( (customHelpLink != null && !FileProviderUtils.exists(context)) || (customHelpLink == null && !packageID.equals(KMManager.KMDefault_UndefinedPackageID)) ) { diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsInstallActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsInstallActivity.java index 3a7a9ce8a7..1775dd0267 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsInstallActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsInstallActivity.java @@ -21,7 +21,6 @@ import android.view.Window; import android.widget.AdapterView; import android.widget.ListAdapter; import android.widget.ListView; -import android.os.Bundle; import android.widget.SimpleAdapter; import android.widget.TextView; @@ -71,13 +70,13 @@ public class KeymanSettingsInstallActivity extends BaseActivity { final String noIcon = "0"; HashMap hashMap = new HashMap<>(); hashMap.put(titleKey, getString(R.string.install_from_keyman_dot_com)); - String icon = String.valueOf(R.drawable.ic_content_add); + String icon = String.valueOf(R.drawable.ic_content_add_mode); hashMap.put(iconKey, icon); installOptionList.add(hashMap); hashMap = new HashMap<>(); hashMap.put(titleKey, getString(R.string.install_from_local_file)); - icon = String.valueOf(R.drawable.ic_folder_open); + icon = String.valueOf(R.drawable.ic_folder_open_mode); hashMap.put(iconKey, icon); hashMap.put(isEnabledKey, "true"); installOptionList.add(hashMap); @@ -95,7 +94,7 @@ public class KeymanSettingsInstallActivity extends BaseActivity { hashMap = new HashMap<>(); hashMap.put(titleKey, getString(R.string.add_languages_to_installed_keyboard)); hashMap.put(subtitleKey, getString(R.string.add_language_subtext)); - icon = String.valueOf(R.drawable.ic_content_add); + icon = String.valueOf(R.drawable.ic_content_add_mode); hashMap.put(iconKey, icon); hashMap.put(isEnabledKey, "true"); installOptionList.add(hashMap); diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/LanguageSettingsActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/LanguageSettingsActivity.java index 9342a0b528..d9c029488b 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/LanguageSettingsActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/LanguageSettingsActivity.java @@ -153,7 +153,8 @@ public final class LanguageSettingsActivity extends BaseActivity { updateActiveLexicalModel(); ImageView imageView = (ImageView) layout.findViewById(R.id.image1); - imageView.setImageResource(R.drawable.ic_action_forward); + //imageView.setImageResource(R.drawable.ic_action_forward); + imageView.setImageResource(R.drawable.ic_action_forward_mode); layout.setEnabled(true); layout.setOnClickListener(new View.OnClickListener() { @Override @@ -333,7 +334,8 @@ public final class LanguageSettingsActivity extends BaseActivity { } holder.text.setText(kbd.getResourceName()); - holder.img.setImageResource(R.drawable.ic_action_forward); + //holder.img.setImageResource(R.drawable.ic_action_forward); + holder.img.setImageResource(R.drawable.ic_action_forward_mode); return convertView; } diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/LanguagesSettingsActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/LanguagesSettingsActivity.java index 79c845fa6f..dd2c61e2b2 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/LanguagesSettingsActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/LanguagesSettingsActivity.java @@ -205,7 +205,8 @@ public final class LanguagesSettingsActivity extends BaseActivity { } holder.textLang.setText(data.name); - holder.img.setImageResource(R.drawable.ic_action_forward); + holder.img.setImageResource(R.drawable.ic_action_forward_mode); +// holder.img.setImageResource(R.drawable.ic_action_forward); holder.textCount.setText(getContext().getResources().getQuantityString(R.plurals.keyboard_count, data.keyboards.size(), data.keyboards.size())); diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index 89ad50d046..a17b32253f 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -14,7 +14,6 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -36,7 +35,6 @@ import com.keyman.engine.cloud.impl.CloudLexicalModelMetaDataDownloadCallback; import com.keyman.engine.data.CloudRepository; import com.keyman.engine.data.Dataset; import com.keyman.engine.data.Keyboard; -import com.keyman.engine.data.LexicalModel; import com.keyman.engine.util.FileUtils; import com.keyman.engine.util.DownloadFileUtils; import com.keyman.android.DownloadIntentService; @@ -47,24 +45,20 @@ import com.keyman.engine.util.WebViewUtils; import com.keyman.engine.util.WebViewUtils.EngineWebViewVersionStatus; import com.keyman.engine.util.WebViewUtils.SystemWebViewStatus; -import android.Manifest; import android.app.ProgressDialog; import android.content.pm.PackageManager; + import android.net.Uri; import android.net.Uri.Builder; import android.os.Build; import android.os.Bundle; -import android.os.Environment; + import android.os.Handler; import android.os.ParcelFileDescriptor; -import android.os.Parcelable; + import android.annotation.SuppressLint; import android.annotation.TargetApi; -import androidx.activity.result.ActivityResult; -import androidx.activity.result.ActivityResultCallback; -import androidx.activity.result.ActivityResultLauncher; -import androidx.activity.result.contract.ActivityResultContracts; import androidx.appcompat.widget.Toolbar; import androidx.appcompat.app.AlertDialog; import androidx.constraintlayout.widget.ConstraintLayout; @@ -73,11 +67,11 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; -import android.content.pm.ResolveInfo; + import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.Canvas; -import android.graphics.Color; + import android.graphics.Paint; import android.graphics.Point; import android.graphics.Rect; @@ -86,10 +80,15 @@ import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import androidx.annotation.NonNull; import androidx.core.app.ActivityCompat; +<<<<<<< Updated upstream +======= +<<<<<<< Updated upstream +import androidx.drawerlayout.widget.DrawerLayout; +======= +import androidx.core.content.ContextCompat; +>>>>>>> Stashed changes +>>>>>>> Stashed changes -import android.provider.Settings; -import android.text.Html; -import android.util.Log; import android.util.TypedValue; import android.view.KeyEvent; import android.view.LayoutInflater; @@ -176,9 +175,21 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene toolbar = (Toolbar) findViewById(R.id.titlebar); setSupportActionBar(toolbar); getSupportActionBar().setTitle(null); +<<<<<<< Updated upstream getSupportActionBar().setDisplayUseLogoEnabled(true); getSupportActionBar().setDisplayShowHomeEnabled(true); getSupportActionBar().setLogo(R.drawable.keyman_logo); +======= +<<<<<<< Updated upstream + getSupportActionBar().setDisplayUseLogoEnabled(false); + getSupportActionBar().setDisplayShowHomeEnabled(false); +======= + getSupportActionBar().setDisplayUseLogoEnabled(true); + getSupportActionBar().setDisplayShowHomeEnabled(true); + getSupportActionBar().setLogo(R.drawable.keyman_logo_mode); +// getSupportActionBar().setLogo(R.drawable.keyman_logo); +>>>>>>> Stashed changes +>>>>>>> Stashed changes getSupportActionBar().setDisplayShowTitleEnabled(false); getSupportActionBar().setBackgroundDrawable(getActionBarDrawable(this)); @@ -672,7 +683,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene LayoutInflater inflater = LayoutInflater.from(MainActivity.this); final View textSizeController = inflater.inflate(R.layout.text_size_controller, null); final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(MainActivity.this); - dialogBuilder.setIcon(R.drawable.ic_light_action_textsize); + dialogBuilder.setIcon(R.drawable.ic_action_textsize_mode); dialogBuilder.setTitle(getTextSizeString()); dialogBuilder.setView(textSizeController); dialogBuilder.setPositiveButton(getString(R.string.label_ok), new DialogInterface.OnClickListener() { @@ -684,6 +695,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene final AlertDialog dialog = dialogBuilder.create(); dialog.show(); + dialog.getWindow().setBackgroundDrawableResource(R.color.dialogBackground); final SeekBar seekBar = (SeekBar) dialog.findViewById(R.id.seekBar); seekBar.setProgress(textSize - minTextSize); @@ -741,7 +753,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene private void showClearTextDialog() { AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(MainActivity.this); - dialogBuilder.setIcon(R.drawable.ic_light_action_trash); + dialogBuilder.setIcon(R.drawable.ic_action_trash_mode); dialogBuilder.setTitle(getString(R.string.action_clear_text)); dialogBuilder.setMessage(getString(R.string.all_text_will_be_cleared)); dialogBuilder.setPositiveButton(getString(R.string.label_ok), new DialogInterface.OnClickListener() { @@ -758,7 +770,10 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } }); - dialogBuilder.show(); + //dialogBuilder.show(); no dialog + final AlertDialog dialog = dialogBuilder.create(); + dialog.show(); + dialog.getWindow().setBackgroundDrawableResource(R.color.dialogBackground); } private void checkGetStarted() { @@ -999,7 +1014,8 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene Paint p = new Paint(); p.setStyle(Paint.Style.FILL); - p.setColor(Color.WHITE); + //p.setColor(Color.BLACK); + p.setColor(ContextCompat.getColor(context,R.color.toolbarColor)); canvas.drawRect(new Rect(0, 0, width, actionBarHeight), p); p.setColor(context.getResources().getColor(R.color.keyman_orange)); diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/SelectLanguageFragment.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/SelectLanguageFragment.java index 03d053e469..71cdfbe642 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/SelectLanguageFragment.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/SelectLanguageFragment.java @@ -6,7 +6,7 @@ package com.tavultesoft.kmapro; import android.content.Context; import android.graphics.Typeface; import android.os.Bundle; -import android.os.Handler; + import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -167,7 +167,7 @@ public final class SelectLanguageFragment extends Fragment implements BlockingSt if (!excludeInstalledLanguages && !isInstallingPackage && KeyboardController.getInstance().keyboardExists( k.getPackageID(), k.getKeyboardID(), k.getLanguageID())) { // If Activity is listing an installed keyboard package, mark installed keyboards with a check - icon = String.valueOf(R.drawable.ic_check); + icon = String.valueOf(R.drawable.ic_check_mode); enable = "false"; } else { // Update title diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/SelectPackageActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/SelectPackageActivity.java index 0b6a3ac9a7..c9bb86faca 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/SelectPackageActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/SelectPackageActivity.java @@ -83,7 +83,7 @@ public final class SelectPackageActivity extends BaseActivity { HashMap hashMap = new HashMap<>(); hashMap.put(titleKey, keyboardName); hashMap.put(subtitleKey, pkgID); - String icon = String.valueOf(R.drawable.ic_action_forward); + String icon = String.valueOf(R.drawable.ic_action_forward_mode); hashMap.put(iconKey, icon); list.add(hashMap); } diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-hdpi/keyman_ic.png b/android/KMAPro/kMAPro/src/main/res/drawable-hdpi/keyman_ic.png new file mode 100644 index 0000000000000000000000000000000000000000..cdd82a9eaf97260117b9500d9bededd68cb46c23 GIT binary patch literal 1686 zcmV;H25I?;P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGf6951U69E94oEQKA1}jNKK~zXf?Uq|? zRM!=T|FzGVi*3gEHbdeV2LT4^f}4<1QmE2~Lb#MhYI;E;X{jnD0uh7~l+;a-$UL-m zq_jnlki=2dsOdw4pbZtl7mBEwaueoeQY1HyViU(>k6|#L`^-7}@55xqzJA5u7Uq61_ z{6f6?4W;DA`F4_|pqrEfu+p8cfBW7ZK$!1yEB2-)0u(I-@G0$DZP zWtKEnPm@^Mg4mECgh#}{+`DCDW`OVExwg7%%fQPBw#ou?4$+F=Oo28S=D?K0G{nSzp<{O{5expO)#3e zIk&QO&tqSjxepNbw(Og2xQ&zj9mi+5+%CokhXkRqyongHwO+XP>iGZ)h3IW?;Vy44 zyp8oTfBh7ZSxEaWT)*Bd7fOf4@7MWb11 z5;K$)5gQvvdSDQ$vI40OKAMtHMDD6EOWTpZM)Zr-^P!BN0)QmO1S6O8W2V4Z2qLls z)9PCRz~Odn6kut-PiaLt>K|SW=bTwUBzmh3FWLp-n< zi(UZ$iuRd8k1VzKH6dBz2J%kb92&d)fK~vYTfLhgGO3y@j0UH)6aY+b1CoE=3!d8Z zMeaiBoE=-vf(#b=0l-M_hj7N?P}9ojY@A0`?#T?0zgc~?`z1{L8flUIOCldmT8lZ# zd^2G;JPg~i43<;gi}W#nPLFoApl5f<0wfR08)u0 zLgy~Y@cutz^xeOpq^uOU(}(2H@IR-|p1bA;xRS~SBP&GhycST+!5>jYW{`Y=q?Pk< zmrppGCgRrvC{c-=Ei-^7-C9+*aL41J$kk6yhXW%pOcToG1OVtr6tUhu)Xbkdlo-14 z!2Nsu-G22!=4Y$O?o^`C=d z;26xA_c0o}CZ&!kn`s>YDA-4!H4;O^67RVJGo1#>1hihUcBs3zFepp$ol<~)`>B%D z2Y-0T>9AVhtXQCvVKE9`fFwsH9=ziPKd@}pmj^}U&QI+&X|G>1aG_&TKmkQNQi$5{ z>$rPo;E=>dn-CpatDvVGULxY9YsL~kpandJj0lqUlO zBlRI>?{@$I*8qUF@jGg!gm`+*z=hxDrTEXGe_qj~TfA-hMPFREdY{bN_oFehW&Yc6 g7m7aB{?F&-UuBQH88iKbWB>pF07*qoM6N<$g1ML`)&Kwi literal 0 HcmV?d00001 diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-hdpi/keyman_text.png b/android/KMAPro/kMAPro/src/main/res/drawable-hdpi/keyman_text.png new file mode 100644 index 0000000000000000000000000000000000000000..2f2891cfb826a7a9845f041c797fcd50586a666d GIT binary patch literal 2683 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 literal 0 HcmV?d00001 diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/branded_logo.xml b/android/KMAPro/kMAPro/src/main/res/drawable/branded_logo.xml index 2f5d1eca2b..23720f7c57 100644 --- a/android/KMAPro/kMAPro/src/main/res/drawable/branded_logo.xml +++ b/android/KMAPro/kMAPro/src/main/res/drawable/branded_logo.xml @@ -3,7 +3,7 @@ android:opacity="opaque"> - + - + \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/branded_logo_dark_mode.xml b/android/KMAPro/kMAPro/src/main/res/drawable/branded_logo_dark_mode.xml new file mode 100644 index 0000000000..9d467ae156 --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/drawable/branded_logo_dark_mode.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_decrement.xml b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_decrement.xml index c3085bec5e..9045283a46 100644 --- a/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_decrement.xml +++ b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_decrement.xml @@ -1,5 +1,6 @@ + diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_forward_mode.xml b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_forward_mode.xml new file mode 100644 index 0000000000..e50c72fd48 --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_forward_mode.xml @@ -0,0 +1,4 @@ + + diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_get_started_mode.xml b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_get_started_mode.xml new file mode 100644 index 0000000000..945b28f2f7 --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_get_started_mode.xml @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_increment.xml b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_increment.xml index ea188f818f..a8e10ac44c 100644 --- a/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_increment.xml +++ b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_increment.xml @@ -1,5 +1,6 @@ + diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_info_mode.xml b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_info_mode.xml new file mode 100644 index 0000000000..cde024c5df --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_info_mode.xml @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_overflow_mode.xml b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_overflow_mode.xml new file mode 100644 index 0000000000..5b1e82c706 --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_overflow_mode.xml @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_share_mode.xml b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_share_mode.xml new file mode 100644 index 0000000000..094bccaf5f --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_share_mode.xml @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_textsize_mode.xml b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_textsize_mode.xml new file mode 100644 index 0000000000..c9bce1b644 --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_textsize_mode.xml @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_timelapse.xml b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_timelapse.xml index 80dc29e33f..c3f9cfdcc8 100644 --- a/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_timelapse.xml +++ b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_timelapse.xml @@ -1,5 +1,8 @@ - + - + + + + diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_trash_mode.xml b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_trash_mode.xml new file mode 100644 index 0000000000..7ce6e6f02e --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_trash_mode.xml @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_check_mode.xml b/android/KMAPro/kMAPro/src/main/res/drawable/ic_check_mode.xml new file mode 100644 index 0000000000..45126f7b91 --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/drawable/ic_check_mode.xml @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_checkbox.xml b/android/KMAPro/kMAPro/src/main/res/drawable/ic_checkbox.xml new file mode 100644 index 0000000000..1b5e326df4 --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/drawable/ic_checkbox.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_cloud_download_mode.xml b/android/KMAPro/kMAPro/src/main/res/drawable/ic_cloud_download_mode.xml new file mode 100644 index 0000000000..dd95be3da2 --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/drawable/ic_cloud_download_mode.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_content_add_mode.xml b/android/KMAPro/kMAPro/src/main/res/drawable/ic_content_add_mode.xml new file mode 100644 index 0000000000..9f14c6d6db --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/drawable/ic_content_add_mode.xml @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_folder_open_mode.xml b/android/KMAPro/kMAPro/src/main/res/drawable/ic_folder_open_mode.xml new file mode 100644 index 0000000000..00896d44cb --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/drawable/ic_folder_open_mode.xml @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_settings_mode.xml b/android/KMAPro/kMAPro/src/main/res/drawable/ic_settings_mode.xml new file mode 100644 index 0000000000..63dacb5436 --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/drawable/ic_settings_mode.xml @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/keyman_logo_mode.xml b/android/KMAPro/kMAPro/src/main/res/drawable/keyman_logo_mode.xml new file mode 100644 index 0000000000..35a6f8acd2 --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/drawable/keyman_logo_mode.xml @@ -0,0 +1,20 @@ + + + + + + + + + + \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/keyman_logo_splash_dark_mode.png b/android/KMAPro/kMAPro/src/main/res/drawable/keyman_logo_splash_dark_mode.png new file mode 100644 index 0000000000000000000000000000000000000000..f26f30c78647e1e3459361ea6532b80f489cb121 GIT binary patch literal 8222 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 + app:tint="@color/iconColor" + /> + app:layout_constraintEnd_toEndOf="parent" + app:tint="@color/iconColor"/> diff --git a/android/KMAPro/kMAPro/src/main/res/layout/activity_select_language.xml b/android/KMAPro/kMAPro/src/main/res/layout/activity_select_language.xml index eea3415394..a14190cf59 100644 --- a/android/KMAPro/kMAPro/src/main/res/layout/activity_select_language.xml +++ b/android/KMAPro/kMAPro/src/main/res/layout/activity_select_language.xml @@ -38,5 +38,6 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintVertical_bias="1.0"/> + diff --git a/android/KMAPro/kMAPro/src/main/res/layout/fragment_select_language.xml b/android/KMAPro/kMAPro/src/main/res/layout/fragment_select_language.xml index df9b471d20..e06f78593b 100644 --- a/android/KMAPro/kMAPro/src/main/res/layout/fragment_select_language.xml +++ b/android/KMAPro/kMAPro/src/main/res/layout/fragment_select_language.xml @@ -5,6 +5,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" + tools:context=".SelectLanguageFragment" > + android:layout_height="wrap_content" + android:padding="3dp"/> + style="?android:attr/actionButtonStyle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/checkbox_margin" + android:layout_centerVertical="true" + android:contentDescription="@string/image_view" + app:tint="@color/iconColor"/> + android:layout_marginStart="@dimen/checkbox_margin_left" + android:src="@drawable/ic_action_get_started" + app:tint="@color/iconColor"/> + + android:src="@drawable/ic_action_close" + app:tint="@color/iconColor"/> diff --git a/android/KMAPro/kMAPro/src/main/res/layout/keyman_settings_layout.xml b/android/KMAPro/kMAPro/src/main/res/layout/keyman_settings_layout.xml index 0b80abd27a..170069d54a 100644 --- a/android/KMAPro/kMAPro/src/main/res/layout/keyman_settings_layout.xml +++ b/android/KMAPro/kMAPro/src/main/res/layout/keyman_settings_layout.xml @@ -13,6 +13,7 @@ android:background="@color/keyman_blue" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" /> + + + + + + + + + + + + diff --git a/android/KMAPro/kMAPro/src/main/res/layout/preference_add_icon_layout.xml b/android/KMAPro/kMAPro/src/main/res/layout/preference_add_icon_layout.xml index 1a8b8af5ad..3b8a933111 100644 --- a/android/KMAPro/kMAPro/src/main/res/layout/preference_add_icon_layout.xml +++ b/android/KMAPro/kMAPro/src/main/res/layout/preference_add_icon_layout.xml @@ -1,5 +1,7 @@ + android:src="@drawable/ic_content_add" + app:tint="@color/iconColor"/> diff --git a/android/KMAPro/kMAPro/src/main/res/layout/preference_duration_icon_layout.xml b/android/KMAPro/kMAPro/src/main/res/layout/preference_duration_icon_layout.xml index c8fd3a48f9..fff11ef254 100644 --- a/android/KMAPro/kMAPro/src/main/res/layout/preference_duration_icon_layout.xml +++ b/android/KMAPro/kMAPro/src/main/res/layout/preference_duration_icon_layout.xml @@ -1,5 +1,7 @@ + android:src="@drawable/ic_action_timelapse" + app:tint="@color/iconColor"/> diff --git a/android/KMAPro/kMAPro/src/main/res/layout/preference_file_browser_layout.xml b/android/KMAPro/kMAPro/src/main/res/layout/preference_file_browser_layout.xml index 9483748278..e81b4fa133 100644 --- a/android/KMAPro/kMAPro/src/main/res/layout/preference_file_browser_layout.xml +++ b/android/KMAPro/kMAPro/src/main/res/layout/preference_file_browser_layout.xml @@ -1,5 +1,7 @@ + android:src="@drawable/ic_folder_open" + app:tint="@color/iconColor"/> diff --git a/android/KMAPro/kMAPro/src/main/res/layout/preference_height_icon_layout.xml b/android/KMAPro/kMAPro/src/main/res/layout/preference_height_icon_layout.xml index be0e7ac155..fe2561723c 100644 --- a/android/KMAPro/kMAPro/src/main/res/layout/preference_height_icon_layout.xml +++ b/android/KMAPro/kMAPro/src/main/res/layout/preference_height_icon_layout.xml @@ -1,14 +1,15 @@ + android:src="@drawable/ic_height" app:tint="@color/iconColor"/> + android:src="@drawable/ic_keyboard" app:tint="@color/iconColor"/> \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/res/layout/preference_icon_layout.xml b/android/KMAPro/kMAPro/src/main/res/layout/preference_icon_layout.xml index 7074c696de..880989b6a0 100644 --- a/android/KMAPro/kMAPro/src/main/res/layout/preference_icon_layout.xml +++ b/android/KMAPro/kMAPro/src/main/res/layout/preference_icon_layout.xml @@ -1,5 +1,6 @@ + android:src="@drawable/ic_action_forward" app:tint="@color/iconColor"/> diff --git a/android/KMAPro/kMAPro/src/main/res/layout/preference_translate_icon_layout.xml b/android/KMAPro/kMAPro/src/main/res/layout/preference_translate_icon_layout.xml index d66b445010..0ad237e47f 100644 --- a/android/KMAPro/kMAPro/src/main/res/layout/preference_translate_icon_layout.xml +++ b/android/KMAPro/kMAPro/src/main/res/layout/preference_translate_icon_layout.xml @@ -1,5 +1,7 @@ + android:src="@drawable/ic_translate" + app:tint="@color/iconColor"/> diff --git a/android/KMAPro/kMAPro/src/main/res/layout/text_size_controller.xml b/android/KMAPro/kMAPro/src/main/res/layout/text_size_controller.xml index cfd36374de..19a407e958 100644 --- a/android/KMAPro/kMAPro/src/main/res/layout/text_size_controller.xml +++ b/android/KMAPro/kMAPro/src/main/res/layout/text_size_controller.xml @@ -1,11 +1,12 @@ + android:paddingTop="25dp"> + android:src="@drawable/textsize_decrease" + app:tint="@color/iconColor"/> + android:src="@drawable/textsize_increase" + app:tint="@color/iconColor"/> diff --git a/android/KMAPro/kMAPro/src/main/res/menu/main.xml b/android/KMAPro/kMAPro/src/main/res/menu/main.xml index 682d98179b..fcfaef95c9 100644 --- a/android/KMAPro/kMAPro/src/main/res/menu/main.xml +++ b/android/KMAPro/kMAPro/src/main/res/menu/main.xml @@ -7,7 +7,8 @@ + android:icon="@drawable/ic_action_share_mode" + /> + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/res/values/colors.xml b/android/KMAPro/kMAPro/src/main/res/values/colors.xml index 2415183a56..b8197e561a 100644 --- a/android/KMAPro/kMAPro/src/main/res/values/colors.xml +++ b/android/KMAPro/kMAPro/src/main/res/values/colors.xml @@ -13,17 +13,27 @@ #00735E #0AB8A0 #79C3DA - #000000 + #FFFFFF #6D6C6F #929396 #C9CACA #DADDDC + @color/analagous_5 @color/analagous_6 @color/complementary_6 + #000 + @color/neutral_1 + @color/neutral_1 + @color/neutral_2 #FFFFFFCF + + @color/neutral_1 @color/complementary_1 + #FFFFFF + @color/keyman_blue + diff --git a/android/KMAPro/kMAPro/src/main/res/values/dimens.xml b/android/KMAPro/kMAPro/src/main/res/values/dimens.xml index 60e89def04..f2e6ac5b3f 100644 --- a/android/KMAPro/kMAPro/src/main/res/values/dimens.xml +++ b/android/KMAPro/kMAPro/src/main/res/values/dimens.xml @@ -18,7 +18,8 @@ 350dp 8dp 8dp - 15dp + + 19dp 10dp 32dp 16dp diff --git a/android/KMAPro/kMAPro/src/main/res/values/styles.xml b/android/KMAPro/kMAPro/src/main/res/values/styles.xml index ae386e0262..da85f8d32a 100644 --- a/android/KMAPro/kMAPro/src/main/res/values/styles.xml +++ b/android/KMAPro/kMAPro/src/main/res/values/styles.xml @@ -6,36 +6,48 @@ Base application theme, dependent on API level. This theme is replaced by AppTheme.Base from res/values-vXX/styles.xml on newer devices. --> - + - --> + + From 6f317448013bb86159e7bae10025e53b2b924a69 Mon Sep 17 00:00:00 2001 From: Mengchou97 Date: Mon, 16 Mar 2026 22:48:08 +0700 Subject: [PATCH 015/140] updated-right-panel-to-origin-without-merging --- android/KMAPro/kMAPro/build.gradle | 3 +- .../com/tavultesoft/kmapro/MainActivity.java | 297 +++++++++++++++++- .../src/main/res/layout/activity_main.xml | 84 +++-- .../src/main/res/layout/nav_checkbox_item.xml | 10 + .../src/main/res/layout/nav_divider.xml | 11 + .../kMAPro/src/main/res/layout/nav_header.xml | 19 ++ .../src/main/res/layout/nav_switch_item.xml | 10 + .../KMAPro/kMAPro/src/main/res/menu/main.xml | 19 +- .../kMAPro/src/main/res/menu/nav_menu.xml | 86 +++++ .../kMAPro/src/main/res/values/strings.xml | 3 + common/web/types/package.json | 2 + 11 files changed, 504 insertions(+), 40 deletions(-) create mode 100644 android/KMAPro/kMAPro/src/main/res/layout/nav_checkbox_item.xml create mode 100644 android/KMAPro/kMAPro/src/main/res/layout/nav_divider.xml create mode 100644 android/KMAPro/kMAPro/src/main/res/layout/nav_header.xml create mode 100644 android/KMAPro/kMAPro/src/main/res/layout/nav_switch_item.xml create mode 100644 android/KMAPro/kMAPro/src/main/res/menu/nav_menu.xml diff --git a/android/KMAPro/kMAPro/build.gradle b/android/KMAPro/kMAPro/build.gradle index 77ab2fa736..766e2cd417 100644 --- a/android/KMAPro/kMAPro/build.gradle +++ b/android/KMAPro/kMAPro/build.gradle @@ -32,7 +32,7 @@ android { defaultConfig { applicationId "com.tavultesoft.kmapro" - minSdkVersion 21 + minSdkVersion 23 targetSdkVersion 35 //println "===DUMPING PROPERTIES===" @@ -184,6 +184,7 @@ dependencies { implementation ('com.github.kenglxn.QRGen:android:3.0.1') { transitive = true } + implementation 'com.google.android.gms:play-services-maps3d:0.2.0' } /*def void dumpProperties(it){ diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index 89ad50d046..c7811e07a1 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -26,6 +26,7 @@ import com.keyman.engine.KMHelpFileActivity; import com.keyman.engine.KMKeyboardDownloaderActivity; import com.keyman.engine.KMManager; import com.keyman.engine.KMManager.KeyboardType; +import com.keyman.engine.KeyboardEventHandler; import com.keyman.engine.KmpInstallMode; import com.keyman.engine.KMTextView; import com.keyman.engine.KeyboardEventHandler.OnKeyboardDownloadEventListener; @@ -65,7 +66,9 @@ import androidx.activity.result.ActivityResult; import androidx.activity.result.ActivityResultCallback; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; +import androidx.appcompat.app.ActionBarDrawerToggle; import androidx.appcompat.widget.Toolbar; +import androidx.appcompat.widget.SwitchCompat; import androidx.appcompat.app.AlertDialog; import androidx.constraintlayout.widget.ConstraintLayout; import android.content.ClipData; @@ -86,6 +89,7 @@ import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import androidx.annotation.NonNull; import androidx.core.app.ActivityCompat; +import androidx.drawerlayout.widget.DrawerLayout; import android.provider.Settings; import android.text.Html; @@ -98,18 +102,33 @@ import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; +import android.view.WindowManager; import android.widget.Button; +import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.SeekBar; import android.widget.TextView; import android.widget.Toast; +import android.view.Gravity; +import android.view.inputmethod.InputMethodManager; +import androidx.drawerlayout.widget.DrawerLayout; +import androidx.appcompat.app.ActionBarDrawerToggle; +import com.google.android.material.navigation.NavigationView; +import androidx.core.view.GravityCompat; +import android.view.MenuItem; +import androidx.appcompat.widget.AppCompatCheckBox; + import io.sentry.android.core.SentryAndroid; public class MainActivity extends BaseActivity implements OnKeyboardEventListener, OnKeyboardDownloadEventListener, ActivityCompat.OnRequestPermissionsResultCallback { public static Context context; + private DrawerLayout drawerLayout; + private ActionBarDrawerToggle drawerToggle; + private NavigationView navigationView; + // Fields used for installing kmp packages public static final int PERMISSION_REQUEST_STORAGE = 0; public static final int READ_REQUEST_CODE = 42; @@ -178,10 +197,94 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene getSupportActionBar().setTitle(null); getSupportActionBar().setDisplayUseLogoEnabled(true); getSupportActionBar().setDisplayShowHomeEnabled(true); + getSupportActionBar().setDisplayShowTitleEnabled(true); +// ImageView logo = new ImageView(this); +// logo.setImageResource(R.drawable.keyman_logo); +// Toolbar.LayoutParams params = new Toolbar.LayoutParams( +// Toolbar.LayoutParams.WRAP_CONTENT, +// Toolbar.LayoutParams.WRAP_CONTENT, +// Gravity.CENTER +// ); +// toolbar.addView(logo, params); getSupportActionBar().setLogo(R.drawable.keyman_logo); - getSupportActionBar().setDisplayShowTitleEnabled(false); getSupportActionBar().setBackgroundDrawable(getActionBarDrawable(this)); + drawerLayout = findViewById(R.id.drawer_layout); + drawerLayout.setScrimColor(Color.parseColor("#80000000")); + // Initialize drawerToggle WITHOUT toolbar to prevent it from showing up at the start + drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, R.string.drawer_open , R.string.drawer_close); + drawerLayout.addDrawerListener(drawerToggle); + drawerToggle.syncState(); + + navigationView = findViewById(R.id.nav_view); + navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() { + @Override + public boolean onNavigationItemSelected(@NonNull MenuItem item) { + int id = item.getItemId(); + if (id == R.id.nav_toggle_show_osk || id == R.id.nav_toggle_haptic_feedback || id == R.id.nav_toggle_send_crash_report) { + toggleDrawerSwitch(navigationView, id); + } else if (id == R.id.nav_checkbox_enable_system_keyboard) { + drawerLayout.closeDrawers(); + startActivity(new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS)); + } else if (id == R.id.nav_checkbox_set_default_keyboard) { + drawerLayout.closeDrawers(); + InputMethodManager imManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); + if (imManager != null) { + imManager.showInputMethodPicker(); + } + } else if (id == R.id.nav_installed_languages) { + drawerLayout.closeDrawers(); + Intent intent = new Intent(context, LanguagesSettingsActivity.class); + intent.putExtra(KMManager.KMKey_DisplayKeyboardSwitcher, false); + startActivity(intent); + } else if (id == R.id.nav_install_keyboard) { + drawerLayout.closeDrawers(); + startActivity(new Intent(context, KeymanSettingsInstallActivity.class)); + } else if (id == R.id.nav_display_language) { + drawerLayout.closeDrawers(); + startActivity(new Intent(context, KeymanSettingsLocalizeActivity.class)); + } else if (id == R.id.nav_keyboard_height) { + drawerLayout.closeDrawers(); + startActivity(new Intent(context, AdjustKeyboardHeightActivity.class)); + } else if (id == R.id.nav_longpress_delay) { + drawerLayout.closeDrawers(); + startActivity(new Intent(context, AdjustLongpressDelayActivity.class)); + } else if (id == R.id.nav_settings) { + drawerLayout.closeDrawers(); + startActivity(new Intent(context, KeymanSettingsActivity.class)); + } else if (id == R.id.nav_about) { + drawerLayout.closeDrawers(); + startActivity(new Intent(context, InfoActivity.class)); + } + return true; + } + }); + initializeDrawerToggleOptions(navigationView); + initializeDrawerCheckboxOptions(navigationView); + refreshDrawerSystemKeyboardCheckboxes(navigationView); + + drawerLayout.addDrawerListener(new DrawerLayout.SimpleDrawerListener() { + + @Override + public void onDrawerSlide(View drawerView, float slideOffset) { + KMManager.hideSystemKeyboard(); + textView.dismissKeyboard(); + } + @Override + public void onDrawerOpened(View drawerView) { + + } + + @Override + public void onDrawerClosed(View drawerView) { + textView.callOnClick(); + } + }); + + drawerToggle.getDrawerArrowDrawable().setColor(Color.BLACK); + drawerToggle.getDrawerArrowDrawable().setBarThickness(10f); + drawerToggle.getDrawerArrowDrawable().setGapSize(10f); + textView = (KMTextView) findViewById(R.id.kmTextView); textView.setText(prefs.getString(userTextKey, "")); textSize = prefs.getInt(userTextSizeKey, minTextSize); @@ -248,6 +351,10 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene protected void onResume() { super.onResume(); + if (navigationView != null) { + refreshDrawerSystemKeyboardCheckboxes(navigationView); + } + if (textView != null) { // Reset inAppPredictionsSuspendedForSensitiveInput flag KMManager.setPredictionsSuspended(textView.getInputType(), KeyboardType.KEYBOARD_TYPE_INAPP); @@ -475,6 +582,14 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene _keyboardupdate.setVisible(false); } + return true; + } else if (item.getItemId() == R.id.action_overflow) { + // Open the drawer from the end when the hamburger (overflow) icon is clicked + if (drawerLayout.isDrawerOpen(GravityCompat.END)) { + drawerLayout.closeDrawer(GravityCompat.END); + } else { + drawerLayout.openDrawer(GravityCompat.END); + } return true; } else { return super.onOptionsItemSelected(item); @@ -485,7 +600,11 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene public boolean onKeyUp(int keycode, KeyEvent e) { switch (keycode) { case KeyEvent.KEYCODE_MENU: - menu.performIdentifierAction(R.id.action_overflow, Menu.FLAG_PERFORM_NO_CLOSE); + if (drawerLayout.isDrawerOpen(GravityCompat.END)) { + drawerLayout.closeDrawer(GravityCompat.END); + } else { + drawerLayout.openDrawer(GravityCompat.END); + } return true; } @@ -970,6 +1089,180 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene startActivity(settingsIntent); } + private void initializeDrawerToggleOptions(NavigationView navigationView) { + SharedPreferences prefs = getSharedPreferences(getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); + + bindDrawerSwitch(navigationView, R.id.nav_toggle_show_osk, + prefs.getBoolean(KeymanSettingsActivity.oskWithPhysicalKeyboardKey, false), + new SwitchChangeHandler() { + @Override + public void onChanged(boolean isChecked) { + SharedPreferences.Editor editor = prefs.edit(); + editor.putBoolean(KeymanSettingsActivity.oskWithPhysicalKeyboardKey, isChecked); + editor.apply(); + } + }); + + bindDrawerSwitch(navigationView, R.id.nav_toggle_haptic_feedback, + prefs.getBoolean(KeymanSettingsActivity.hapticFeedbackKey, false), + new SwitchChangeHandler() { + @Override + public void onChanged(boolean isChecked) { + SharedPreferences.Editor editor = prefs.edit(); + editor.putBoolean(KeymanSettingsActivity.hapticFeedbackKey, isChecked); + editor.apply(); + KMManager.setHapticFeedback(isChecked); + } + }); + + bindDrawerSwitch(navigationView, R.id.nav_toggle_send_crash_report, + prefs.getBoolean(KeymanSettingsActivity.sendCrashReport, true), + new SwitchChangeHandler() { + @Override + public void onChanged(boolean isChecked) { + SharedPreferences.Editor editor = prefs.edit(); + editor.putBoolean(KeymanSettingsActivity.sendCrashReport, isChecked); + editor.apply(); + KMManager.setMaySendCrashReport(isChecked); + } + }); + } + + private void initializeDrawerCheckboxOptions(NavigationView navigationView) { + bindDrawerCheckboxAction(navigationView, R.id.nav_checkbox_enable_system_keyboard, + new Runnable() { + @Override + public void run() { + startActivity(new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS)); + } + }); + + bindDrawerCheckboxAction(navigationView, R.id.nav_checkbox_set_default_keyboard, + new Runnable() { + @Override + public void run() { + InputMethodManager imManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); + if (imManager != null) { + imManager.showInputMethodPicker(); + } + } + }); + } + + private void refreshDrawerSystemKeyboardCheckboxes(NavigationView navigationView) { + setDrawerCheckboxState(navigationView, R.id.nav_checkbox_enable_system_keyboard, + SystemIMESettings.isEnabledAsSystemKB(context)); + setDrawerCheckboxState(navigationView, R.id.nav_checkbox_set_default_keyboard, + SystemIMESettings.isDefaultKB(context)); + } + + private void bindDrawerSwitch(NavigationView navigationView, int menuItemId, boolean defaultValue, + final SwitchChangeHandler switchChangeHandler) { + MenuItem menuItem = navigationView.getMenu().findItem(menuItemId); + if (menuItem == null) { + return; + } + + menuItem.setCheckable(false); + + View actionView = menuItem.getActionView(); + if (actionView == null) { + return; + } + + final SwitchCompat switchView; + if (actionView instanceof SwitchCompat) { + switchView = (SwitchCompat) actionView; + } else { + switchView = actionView.findViewById(R.id.nav_switch); + } + + if (switchView == null) { + return; + } + + switchView.setChecked(defaultValue); + switchView.setOnCheckedChangeListener((buttonView, isChecked) -> switchChangeHandler.onChanged(isChecked)); + actionView.setOnClickListener(v -> switchView.toggle()); + } + + private void toggleDrawerSwitch(NavigationView navigationView, int menuItemId) { + MenuItem menuItem = navigationView.getMenu().findItem(menuItemId); + if (menuItem == null) { + return; + } + + View actionView = menuItem.getActionView(); + if (actionView == null) { + return; + } + + if (actionView instanceof SwitchCompat) { + ((SwitchCompat) actionView).toggle(); + return; + } + + SwitchCompat switchView = actionView.findViewById(R.id.nav_switch); + if (switchView != null) { + switchView.toggle(); + } + } + + private void bindDrawerCheckboxAction(NavigationView navigationView, int menuItemId, final Runnable onActivate) { + MenuItem menuItem = navigationView.getMenu().findItem(menuItemId); + if (menuItem == null) { + return; + } + + menuItem.setCheckable(false); + + View actionView = menuItem.getActionView(); + if (actionView == null) { + return; + } + + final AppCompatCheckBox checkBox; + if (actionView instanceof AppCompatCheckBox) { + checkBox = (AppCompatCheckBox) actionView; + } else { + checkBox = actionView.findViewById(R.id.nav_checkbox); + } + + if (checkBox == null) { + return; + } + + checkBox.setOnClickListener(v -> onActivate.run()); + actionView.setOnClickListener(v -> onActivate.run()); + } + + private void setDrawerCheckboxState(NavigationView navigationView, int menuItemId, boolean isChecked) { + MenuItem menuItem = navigationView.getMenu().findItem(menuItemId); + if (menuItem == null) { + return; + } + + View actionView = menuItem.getActionView(); + if (actionView == null) { + return; + } + + AppCompatCheckBox checkBox; + if (actionView instanceof AppCompatCheckBox) { + checkBox = (AppCompatCheckBox) actionView; + } else { + checkBox = actionView.findViewById(R.id.nav_checkbox); + } + + if (checkBox != null) { + checkBox.setChecked(isChecked); + } + } + + private interface SwitchChangeHandler { + void onChanged(boolean isChecked); + } + /** * Dismiss the download progress dialog */ diff --git a/android/KMAPro/kMAPro/src/main/res/layout/activity_main.xml b/android/KMAPro/kMAPro/src/main/res/layout/activity_main.xml index ffcd4a2421..e31fa00e0c 100644 --- a/android/KMAPro/kMAPro/src/main/res/layout/activity_main.xml +++ b/android/KMAPro/kMAPro/src/main/res/layout/activity_main.xml @@ -1,42 +1,62 @@ - + android:fitsSystemWindows="true"> - - - + android:layout_height="match_parent" + android:layout_above="@+id/KMKeyboard" + android:orientation="vertical" + android:background="@android:color/white" + tools:context=".MainActivity" + android:fitsSystemWindows="true"> - + - - + - + + + + + + + + + + + diff --git a/android/KMAPro/kMAPro/src/main/res/layout/nav_checkbox_item.xml b/android/KMAPro/kMAPro/src/main/res/layout/nav_checkbox_item.xml new file mode 100644 index 0000000000..6988d63664 --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/layout/nav_checkbox_item.xml @@ -0,0 +1,10 @@ + + diff --git a/android/KMAPro/kMAPro/src/main/res/layout/nav_divider.xml b/android/KMAPro/kMAPro/src/main/res/layout/nav_divider.xml new file mode 100644 index 0000000000..037e4e3b27 --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/layout/nav_divider.xml @@ -0,0 +1,11 @@ + + + diff --git a/android/KMAPro/kMAPro/src/main/res/layout/nav_header.xml b/android/KMAPro/kMAPro/src/main/res/layout/nav_header.xml new file mode 100644 index 0000000000..a59be8fd9d --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/layout/nav_header.xml @@ -0,0 +1,19 @@ + + + + + + \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/res/layout/nav_switch_item.xml b/android/KMAPro/kMAPro/src/main/res/layout/nav_switch_item.xml new file mode 100644 index 0000000000..af00b3fe78 --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/layout/nav_switch_item.xml @@ -0,0 +1,10 @@ + + diff --git a/android/KMAPro/kMAPro/src/main/res/menu/main.xml b/android/KMAPro/kMAPro/src/main/res/menu/main.xml index 682d98179b..631af2970a 100644 --- a/android/KMAPro/kMAPro/src/main/res/menu/main.xml +++ b/android/KMAPro/kMAPro/src/main/res/menu/main.xml @@ -4,18 +4,23 @@ xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity"> - + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/res/values/strings.xml b/android/KMAPro/kMAPro/src/main/res/values/strings.xml index 0423bdc015..bf53ca935a 100644 --- a/android/KMAPro/kMAPro/src/main/res/values/strings.xml +++ b/android/KMAPro/kMAPro/src/main/res/values/strings.xml @@ -6,6 +6,9 @@ Share + Open navigation drawer + Close navigation drawer + Web Browser diff --git a/common/web/types/package.json b/common/web/types/package.json index 2b2122f682..4c2e2bfd2e 100644 --- a/common/web/types/package.json +++ b/common/web/types/package.json @@ -36,6 +36,8 @@ "restructure": "3.0.1" }, "devDependencies": { + "@types/mocha": "^5.2.7", + "@types/node": "^20.4.1", "ajv": "^8.12.0", "ajv-cli": "^5.0.0", "ajv-formats": "^2.1.1", From 776b5656cb7fdd8b9b40977189374bb66c66cd3d Mon Sep 17 00:00:00 2001 From: Mengchou97 Date: Mon, 16 Mar 2026 23:31:06 +0700 Subject: [PATCH 016/140] change section title --- .../com/keyman/android/SystemKeyboard.java | 352 -------- .../src/main/res/layout/nav_divider.xml | 1 - .../kMAPro/src/main/res/menu/nav_menu.xml | 4 +- .../chromium/Keyman.UI.UframeCEFHost.pas | 789 ------------------ 4 files changed, 2 insertions(+), 1144 deletions(-) delete mode 100644 android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java delete mode 100644 common/windows/delphi/chromium/Keyman.UI.UframeCEFHost.pas diff --git a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java deleted file mode 100644 index 16330bfcda..0000000000 --- a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java +++ /dev/null @@ -1,352 +0,0 @@ -/** - * Copyright (C) 2017 SIL International. All rights reserved. - */ - -package com.keyman.android; - -import com.keyman.engine.util.DownloadFileUtils; -import com.tavultesoft.kmapro.BuildConfig; -import com.tavultesoft.kmapro.DefaultLanguageResource; -import com.tavultesoft.kmapro.KeymanSettingsActivity; -import com.tavultesoft.kmapro.PreferencesManager; -import com.keyman.engine.KMManager; -import com.keyman.engine.KMManager.KeyboardType; -import com.keyman.engine.KMHardwareKeyboardInterpreter; -import com.keyman.engine.KMManager.SuggestionType; -import com.keyman.engine.KeyboardEventHandler.OnKeyboardEventListener; -import com.keyman.engine.R; -import com.keyman.engine.data.Keyboard; -import com.keyman.engine.util.DependencyUtil; -import com.keyman.engine.util.DependencyUtil.LibraryType; - -import android.content.Context; -import android.content.SharedPreferences; -import android.content.res.Configuration; -import android.graphics.Point; -import android.inputmethodservice.InputMethodService; -import android.text.InputType; -import android.util.Log; -import android.view.KeyEvent; -import android.view.View; -import android.view.ViewGroup; -import android.view.Window; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.ExtractedText; -import android.view.inputmethod.ExtractedTextRequest; -import android.view.inputmethod.InputConnection; -import android.widget.FrameLayout; -import android.content.Intent; - -import io.sentry.android.core.SentryAndroid; -import io.sentry.Sentry; - -public class SystemKeyboard extends InputMethodService implements OnKeyboardEventListener { - - private static View inputView = null; - private KMHardwareKeyboardInterpreter interpreter = null; - private int inputType = InputType.TYPE_NULL; - private int lastOrientation = Configuration.ORIENTATION_UNDEFINED; - - private static final String TAG = "SystemKeyboard"; - - /** - * Main initialization of the input method component. Be sure to call - * to super class. - */ - @Override - public void onCreate() { - super.onCreate(); - - if (DependencyUtil.libraryExists(LibraryType.SENTRY) && !Sentry.isEnabled()) { - Log.d(TAG, "Initializing Sentry"); - SentryAndroid.init(getApplicationContext(), options -> { - options.setEnableAutoSessionTracking(false); - options.setRelease(com.tavultesoft.kmapro.BuildConfig.KEYMAN_VERSION_GIT_TAG); - options.setEnvironment(com.tavultesoft.kmapro.BuildConfig.KEYMAN_VERSION_ENVIRONMENT); - }); - } - if (BuildConfig.DEBUG) { - KMManager.setDebugMode(true); - } - KMManager.addKeyboardEventListener(this); - Context context = getApplicationContext(); - KMManager.initialize(context, KeyboardType.KEYBOARD_TYPE_SYSTEM); - DefaultLanguageResource.install(context); - interpreter = new KMHardwareKeyboardInterpreter(context, KeyboardType.KEYBOARD_TYPE_SYSTEM); - KMManager.setInputMethodService(this); // for HW interface - - SharedPreferences prefs = context.getSharedPreferences(context.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); - KMManager.SpacebarText spacebarText = KMManager.SpacebarText.fromString(prefs.getString(KeymanSettingsActivity.spacebarTextKey, KMManager.SpacebarText.LANGUAGE_KEYBOARD.toString())); - KMManager.setSpacebarText(spacebarText); - - // Set the system keyboard HTML banner - BannerController.setHTMLBanner(this, KeyboardType.KEYBOARD_TYPE_SYSTEM); - - boolean mayHaveHapticFeedback = prefs.getBoolean(KeymanSettingsActivity.hapticFeedbackKey, false); - KMManager.setHapticFeedback(mayHaveHapticFeedback); - - // Checking for updates should never be allowed to crash the keyboard. - // Just silently fail if this occurs. - if(DownloadFileUtils.getDownloadManager(this) != null) { - // Will try to emit a toast if it fails - i.e., is not silent. - KMManager.executeResourceUpdate(this); - } - } - - @Override - public void onDestroy() { - inputView = null; - KMManager.removeKeyboardEventListener(this); - interpreter = null; // Throw it away, since we're losing our application's context. - KMManager.onDestroy(); - super.onDestroy(); - } - - /** - * This is the point where you can do all of your UI initialization. It - * is called after creation and any configuration change. - */ - @Override - public void onInitializeInterface() { - super.onInitializeInterface(); - } - - /** - * Called by the framework when your view for creating input needs to - * be generated. This will be called the first time your input method - * is displayed, and every time it needs to be re-created such as due to - * a configuration change. - */ - @Override - public View onCreateInputView() { - if (inputView == null) { - inputView = KMManager.createInputView(this); - } - - ViewGroup parent = (ViewGroup) inputView.getParent(); - if (parent != null) { - parent.removeView(inputView); - } - - return inputView; - } - - /** - * Deal with the editor reporting movement of its cursor. - */ - @Override - public void onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd) { - super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, candidatesStart, candidatesEnd); - KMManager.updateSelectionRange(KMManager.KeyboardType.KEYBOARD_TYPE_SYSTEM); - } - - private void sendCurrentFontName() { - Keyboard keyboardInfo = KMManager.getCurrentKeyboardInfo(this); - if (keyboardInfo != null) { - String fontName = keyboardInfo.getFont(); - - Intent intent; - if (BuildConfig.DEBUG) { - intent = new Intent("com.tavultesoft.kmapro.debug.keyboard_changed"); - } else { - intent = new Intent("com.tavultesoft.kmapro.keyboard_changed"); - } - intent.putExtra("fontName", fontName); - sendBroadcast(intent); - } - } - - /** - * This is the main point where we do our initialization of the input method - * to begin operating on an application. At this point we have been - * bound to the client, and are now receiving all of the detailed information - * about the target of our edits. - */ - @Override - public void onStartInput(EditorInfo attribute, boolean restarting) { - attribute.imeOptions |= EditorInfo.IME_FLAG_NO_EXTRACT_UI | EditorInfo.IME_FLAG_NO_FULLSCREEN; - super.onStartInput(attribute, restarting); - KMManager.onStartInput(attribute, restarting); - KMManager.resetContext(KeyboardType.KEYBOARD_TYPE_SYSTEM); - - Context appContext = getApplicationContext(); - // Temporarily disable predictions on certain fields (e.g. hidden password field or numeric) - inputType = attribute.inputType; - KMManager.setPredictionsSuspended(inputType, KeyboardType.KEYBOARD_TYPE_SYSTEM); - if (KMManager.getPredictionsSuspended(KeyboardType.KEYBOARD_TYPE_SYSTEM)) { - KMManager.setBannerOptions(false); - // Set the system keyboard HTML banner - BannerController.setHTMLBanner(this, KeyboardType.KEYBOARD_TYPE_SYSTEM); - } else if (KMManager.isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_SYSTEM)){ - // Check if predictions needs to be re-enabled per Settings preference - Keyboard kbInfo = KMManager.getCurrentKeyboardInfo(appContext); - if (kbInfo != null) { - String langId = kbInfo.getLanguageID(); - SharedPreferences prefs = appContext.getSharedPreferences(appContext.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); - int maySuggest = prefs.getInt(KMManager.getLanguageAutoCorrectionPreferenceKey(langId), KMManager.KMDefault_Suggestion); - // Enable banner if maySuggest is not SuggestionType.SUGGESTIONS_DISABLED (0) - KMManager.setBannerOptions(maySuggest != SuggestionType.SUGGESTIONS_DISABLED.toInt()); - } else { - KMManager.setBannerOptions(false); - } - } - - // Determine special handling for ENTER key - KMManager.setEnterMode(attribute.imeOptions, inputType); - - InputConnection ic = getCurrentInputConnection(); - if (ic != null) { - ExtractedText icText = ic.getExtractedText(new ExtractedTextRequest(), 0); - /* - We do sometimes receive null `icText.text`, even though - getExtractedText() docs does not list this as a possible - return value, so we test for that as well (#11479) - */ - if (icText != null && icText.text != null) { - // Update the text selection but ignore the returned statuses - KMManager.updateText(KeyboardType.KEYBOARD_TYPE_SYSTEM, icText.text.toString()); - KMManager.updateSelectionRange(KeyboardType.KEYBOARD_TYPE_SYSTEM); - } - } - - // Select numeric layer if applicable - if (KMManager.isNumericField(inputType)) { - KMManager.setNumericLayer(KeyboardType.KEYBOARD_TYPE_SYSTEM); - } - - if (KMManager.isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_SYSTEM)) { - sendCurrentFontName(); - } - } - - @Override - public void onStartInputView(EditorInfo attribute, boolean restarting) { - super.onStartInputView(attribute, restarting); - setInputView(onCreateInputView()); - - // Update the input type - inputType = attribute.inputType; - } - - @Override - public void onUpdateExtractingVisibility(EditorInfo ei) { - super.onUpdateExtractingVisibility(ei); - } - - @Override - public void onConfigureWindow(Window win, boolean isFullscreen, boolean isCandidatesOnly) { - super.onConfigureWindow(win, isFullscreen, isCandidatesOnly); - - // We don't currently use isFullscreen or isCandidatesOnly; we always want to MATCH_PARENT, - // unlike the default for height which is WRAP_CONTENT. We then adjust the touchable area - // in `onCalculateInsets` - win.setLayout(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT); - } - - @Override - public void onComputeInsets(InputMethodService.Insets outInsets) { - super.onComputeInsets(outInsets); - - // This method (likely) includes the IME equivalent to `onResume` for `Activity`-based classes, - // making it an important time to detect orientation changes. - Context appContext = getApplicationContext(); - int newOrientation = KMManager.getOrientation(appContext); - if(newOrientation != lastOrientation) { - lastOrientation = newOrientation; - Configuration newConfig = this.getResources().getConfiguration(); - KMManager.onConfigurationChanged(newConfig); - } - - // Update the touchable region of the Keyman keyboard - Point size = KMManager.getWindowSize(getApplicationContext()); - - int inputViewHeight = 0; - if (inputView != null) { - inputViewHeight = inputView.getHeight(); - } - - int navigationHeight = KMManager.getNavigationBarHeight(this, KeyboardType.KEYBOARD_TYPE_SYSTEM); - int bannerHeight = KMManager.getBannerHeight(this); - int kbHeight = KMManager.getKeyboardHeight(this); - outInsets.contentTopInsets = inputViewHeight - bannerHeight - kbHeight - navigationHeight; - outInsets.visibleTopInsets = outInsets.contentTopInsets; - outInsets.touchableInsets = InputMethodService.Insets.TOUCHABLE_INSETS_REGION; - outInsets.touchableRegion.set(0, outInsets.contentTopInsets, size.x, size.y); - } - - @Override - public void onKeyboardLoaded(KeyboardType keyboardType) { - // Do nothing - } - - @Override - public void onKeyboardChanged(String newKeyboard) { - // Refresh banner theme - BannerController.setHTMLBanner(this, KeyboardType.KEYBOARD_TYPE_SYSTEM); - KMManager.showSystemKeyboard(); - sendCurrentFontName(); - } - - @Override - public void onKeyboardShown() { - // Refresh banner theme - BannerController.setHTMLBanner(this, KeyboardType.KEYBOARD_TYPE_SYSTEM); - } - - @Override - public void onKeyboardDismissed() { - // Do nothing - } - - @Override - public boolean onEvaluateInputViewShown() { - // On Android API 36+, the OSK defaults to not appearing when a physical keyboard is connected. - // If the default implementation returns true, recommend honoring it - // Reference: https://android.googlesource.com/platform/frameworks/base/+/7b739a8%5E%21/ - if (super.onEvaluateInputViewShown()) { - return true; - }; - - Context context = getApplicationContext(); - SharedPreferences prefs = context.getSharedPreferences(PreferencesManager.kma_prefs_name, Context.MODE_PRIVATE); - boolean showOSK = prefs.getBoolean(KeymanSettingsActivity.oskWithPhysicalKeyboardKey, false); - return showOSK; - } - - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - // Determine if Physical keystroke should be passed off - if (inputType == InputType.TYPE_NULL) { - return false; // Revert to default handling - } - - if (event.getAction() == KeyEvent.ACTION_DOWN) { - switch (keyCode) { - case KeyEvent.KEYCODE_BACK: - // Dismiss the keyboard if currently shown - if (isInputViewShown()) { - KMManager.hideSystemKeyboard(); - return true; - } - break; - } - } - - return interpreter.onKeyDown(keyCode, event); // if false, will revert to default handling. - } - - @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { - return interpreter.onKeyUp(keyCode, event); - } - - @Override - public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) { - return interpreter.onKeyMultiple(keyCode, count, event); - } - - @Override - public boolean onKeyLongPress(int keyCode, KeyEvent event) { - return interpreter.onKeyLongPress(keyCode, event); - } -} \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/res/layout/nav_divider.xml b/android/KMAPro/kMAPro/src/main/res/layout/nav_divider.xml index 037e4e3b27..192100c926 100644 --- a/android/KMAPro/kMAPro/src/main/res/layout/nav_divider.xml +++ b/android/KMAPro/kMAPro/src/main/res/layout/nav_divider.xml @@ -7,5 +7,4 @@ android:orientation="vertical" android:layout_marginBottom="2dp" android:background="@color/keyman_orange"> - diff --git a/android/KMAPro/kMAPro/src/main/res/menu/nav_menu.xml b/android/KMAPro/kMAPro/src/main/res/menu/nav_menu.xml index a750588f65..76b7cfee8d 100644 --- a/android/KMAPro/kMAPro/src/main/res/menu/nav_menu.xml +++ b/android/KMAPro/kMAPro/src/main/res/menu/nav_menu.xml @@ -8,7 +8,7 @@ - + android:title="Preferences"> MainThreadID); -end; - -procedure TframeCEFHost.StartClose; -begin - AssertVclThread; - Visible := False; - FIsClosing := True; - cef.CloseBrowser(True); -end; - -procedure TframeCEFHost.StartShutdown(CompletionHandler: TShutdownCompletionHandlerEvent); -begin - AssertVclThread; - OutputDebugString(PChar('TframeCEFHost.StartShutdown')); - FIsClosing := True; - FShutdownCompletionHandler := CompletionHandler; - - // If the browser has not been initialized, we'll not get the close signal, so we - // post it to occur on next idle. - if cef.Initialized - then cef.CloseBrowser(False) - else PostMessage(FCallbackWnd, CEF_AFTERDESTROY, 0, 0); -end; - -procedure TframeCEFHost.FormCreate(Sender: TObject); -begin - AssertVclThread; - inherited; - FApplicationHandle := Application.Handle; // take a copy to avoid Vcl thread mismatches in cef callbacks - - // We need our own window handle for events, because VCL windows can be destroyed - // and recreated at any time. With our own handle, we can guarantee the lifetime - // of it across threads. - FCallbackWnd := AllocateHWnd(CallbackWndProc); - FInitializeCEF.RegisterWindow(Self); -// CreateBrowser; -end; - -procedure TframeCEFHost.FormDestroy(Sender: TObject); -begin - AssertVclThread; -// OutputDebugString(PChar('TframeCEFHost.FormDestroy')); - inherited; - FInitializeCEF.UnregisterWindow(Self); - DeallocateHWnd(FCallbackWnd); -end; - -procedure TframeCEFHost.FormShow(Sender: TObject); -begin - AssertVclThread; - inherited; - PostMessage(FCallbackWnd, CEF_SHOW, 0, 0); -end; - -function TframeCEFHost.GetDebugInfo: string; -begin - Result := FNextURL; - if Assigned(Owner) then Result := Owner.ClassName+':'+Result; -end; - -function TframeCEFHost.HasFocus: Boolean; -begin - AssertVclThread; - Result := Assigned(cefwp) and cefwp.HandleAllocated and IsChild(cefwp.Handle, GetFocus); -end; - -procedure TframeCEFHost.Handle_CEF_SHOW(var message: TMessage); -begin - AssertVclThread; - CreateBrowser; -end; - -procedure TframeCEFHost.Handle_CEF_TITLECHANGE(var message: TMessage); -var - p: PCEFTitleChangeEventData; -begin - AssertVclThread; - p := PCEFTitleChangeEventData(message.LParam); - - if Assigned(FOnTitleChange) then - FOnTitleChange(Self, p.title); - - FreeMem(p); -end; - -procedure TframeCEFHost.cefWidgetCompMsg(Sender: TObject; var aMessage: TMessage; - var aHandled: Boolean); -begin - AssertCefThread; - if aMessage.Msg = WM_SETFOCUS then - PostMessage(FCallbackWnd, CEF_SETFOCUS, 0, 0); -end; - -procedure TframeCEFHost.CreateBrowser; -begin - AssertVclThread; - FIsCreated := True; - tmrCreateBrowser.Enabled := not cef.CreateBrowser(cefwp); -end; - -procedure TframeCEFHost.Navigate(const url: string); -begin - AssertVclThread; - FNextURL := url; - Navigate; -end; - -procedure TframeCEFHost.Navigate; -begin - AssertVclThread; - if FNextURL = '' then - Exit; - - if not FIsCreated then - begin - cef.DefaultUrl := FNextURL; - FNextURL := ''; - Exit; - end; - - if not cef.Initialized then - begin - // After initialization, refresh will happen - // See cefAfterCreated - Exit; - end; - - cef.LoadURL(FNextURL); -end; - -procedure TframeCEFHost.SetFocus; -begin - AssertVclThread; - if not FIsClosing and Assigned(cefwp) and Assigned(cef) and cefwp.CanFocus then - begin - GetParentForm(Self).ActiveControl := Self; - cef.SetFocus(True); - end; -end; - -procedure TframeCEFHost.CallbackWndProc(var Message: TMessage); -begin - AssertVclThread; - case Message.Msg of - CEF_DESTROY: Handle_CEF_DESTROY(Message); - CEF_AFTERDESTROY: Handle_CEF_AFTERDESTROY(Message); - CEF_AFTERCREATE: Handle_CEF_AFTERCREATE(Message); - CEF_SHOW: Handle_CEF_SHOW(Message); - CEF_LOADEND: Handle_CEF_LOADEND(Message); - CEF_KEYEVENT: Handle_CEF_KEYEVENT(Message); - CEF_BEFOREBROWSE: Handle_CEF_BEFOREBROWSE(Message); - CEF_TITLECHANGE: Handle_CEF_TITLECHANGE(Message); - CEF_COMMAND: Handle_CEF_COMMAND(Message); - CEF_SETFOCUS: Handle_CEF_SETFOCUS(Message); - CEF_LOADINGSTATECHANGE: Handle_CEF_LOADINGSTATECHANGE(Message); - end; - - if Self <> nil then - Message.Result := DefWindowProc(FCallbackWnd, Message.Msg, Message.WParam, Message.LParam); -end; - -procedure TframeCEFHost.Handle_CEF_AFTERCREATE(var Message: TMessage); -begin - AssertVclThread; - Navigate; - if Assigned(FOnAfterCreated) then - FOnAfterCreated(Self); -end; - -procedure TframeCEFHost.cefAfterCreated(Sender: TObject; - const browser: ICefBrowser); -begin - AssertCefThread; - PostMessage(FCallbackWnd, CEF_AFTERCREATE, 0, 0); -end; - -procedure TframeCEFHost.Handle_CEF_AFTERDESTROY(var Message: TMessage); -begin - AssertVclThread; - if Assigned(FShutdownCompletionHandler) then - begin - FShutdownCompletionHandler(Self); - FShutdownCompletionHandler := nil; - end; -end; - -function IsLocalURL(URL: string): Boolean; -begin - Result := - URL.StartsWith('file:') or - URL.StartsWith('/') or - URL.StartsWith('http://localhost:') or - URL.StartsWith('http://localhost/') or - URL.StartsWith('http://127.0.0.1:') or - URL.StartsWith('http://127.0.0.1/'); -end; - -function IsPDFURL(URL: string): Boolean; -begin - Result := LowerCase(URL).EndsWith('.pdf'); -end; - -procedure TframeCEFHost.Handle_CEF_BEFOREBROWSE(var message: TMessage); -var - params: TStringList; - url: string; - isMain, isPopup, wasHandled, - shouldOpenUrlIfNotHandled: Boolean; -begin - AssertVclThread; - - params := TStringList(message.LParam); - url := params[0]; - wasHandled := (message.WParam and 1) = 1; - shouldOpenUrlIfNotHandled := (message.WParam and 2) = 2; - isPopup := (message.WParam and 4) = 4; - isMain := (message.WParam and 8) = 8; - - if wasHandled then - begin - if Assigned(FOnBeforeBrowseEx) then - begin - FOnBeforeBrowseEx(Self, url, isMain, isPopup, wasHandled); - end - else if Assigned(FOnBeforeBrowse) then - begin - FOnBeforeBrowse(Self, url, isPopup, wasHandled); - end; - - if FShouldOpenRemoteUrlsInBrowser and (not IsLocalURL(URL) or IsPDFURL(URL)) then - begin - {$MESSAGE HINT 'Refactor how remote URLs are handled'} - // TODO: refactor links - TUtilExecute.URL(url); - end; - end - else if shouldOpenUrlIfNotHandled then - cef.LoadURL(url); - - params.Free; -end; - -procedure TframeCEFHost.Handle_CEF_COMMAND(var message: TMessage); -var - params: TStringList; - command: string; -begin - AssertVclThread; - params := TStringList(message.LParam); - - if Assigned(FOnCommand) then - begin - params.Delete(0); // url; not really used currently - command := params[0]; - params.Delete(0); - FOnCommand(Self, command, params); - end; - - params.Free; -end; - -procedure TframeCEFHost.cefBeforeBrowse(Sender: TObject; - const browser: ICefBrowser; const frame: ICefFrame; - const request: ICefRequest; user_gesture, isRedirect: Boolean; - out Result: Boolean); -begin - AssertCefThread; - - DoBeforeBrowse(request.Url, frame.IsMain, False, False, Result); -end; - -procedure TframeCEFHost.DoBeforeBrowse(const url: string; isMain, isPopup, ShouldOpenUrlIfNotHandled: Boolean; out Handled: Boolean); -var - params: TStringList; - wParam: DWORD; -begin - AssertCefThread; - - Handled := False; - - if Assigned(FOnBeforeBrowseExSync) then - begin - FOnBeforeBrowseExSync(Self, url, isMain, isPopup, Handled); - end - else if Assigned(FOnBeforeBrowseSync) then - begin - FOnBeforeBrowseSync(Self, url, isPopup, Handled); - end; - - if not Handled and GetParamsFromURL(Url, params) then - begin - Handled := True; - // Use OnCommand for keyman: URLs - params.Insert(0, Url); - PostMessage(FCallbackWnd, CEF_COMMAND, 0, LPARAM(params)); - end - else - begin - // Use OnBeforeBrowse for other URLs - if not Handled and FShouldOpenRemoteUrlsInBrowser and (not IsLocalURL(URL) or IsPDFURL(URL)) then - Handled := True; - - params := TStringList.Create; - params.Add(Url); - wParam := 0; - if Handled then - wParam := wParam or 1; - if ShouldOpenUrlIfNotHandled then - wParam := wParam or 2; - if isPopup then - wParam := wParam or 4; - if isMain then - wParam := wParam or 8; - PostMessage(FCallbackWnd, CEF_BEFOREBROWSE, wParam, LPARAM(params)); - end; -end; - -procedure TframeCEFHost.cefBeforeClose(Sender: TObject; const browser: ICefBrowser); -begin - AssertCefThread; - PostMessage(FCallbackWnd, CEF_AFTERDESTROY, 0, 0); -end; - -procedure TframeCEFHost.cefClose(Sender: TObject; const browser: ICefBrowser; - var aAction: TCefCloseBrowserAction); -begin - AssertCefThread; - PostMessage(FCallbackWnd, CEF_DESTROY, 0, 0); - aAction := cbaClose; -end; - -procedure TframeCEFHost.Handle_CEF_DESTROY(var Message: TMessage); -begin - AssertVclThread; - if Assigned(cefwp) then - cefwp.DestroyChildWindow; - FreeAndNil(cefwp); -end; - -procedure TframeCEFHost.tmrCreateBrowserTimer(Sender: TObject); -begin - AssertVclThread; - tmrCreateBrowser.Enabled := False; - CreateBrowser; -end; - -procedure TframeCEFHost.cefLoadEnd(Sender: TObject; const browser: ICefBrowser; - const frame: ICefFrame; httpStatusCode: Integer); -begin - AssertCefThread; - PostMessage(FCallbackWnd, CEF_LOADEND, WPARAM(httpStatusCode), 0); -end; - -procedure TframeCEFHost.cefLoadingStateChange(Sender: TObject; - const browser: ICefBrowser; isLoading, canGoBack, canGoForward: Boolean); -var - v: Integer; -begin - AssertCefThread; - v := 0; - if isLoading then v := v or CEF_LOADINGSTATECHANGE_ISLOADING; - if canGoBack then v := v or CEF_LOADINGSTATECHANGE_CANGOBACK; - if canGoForward then v := v or CEF_LOADINGSTATECHANGE_CANGOFORWARD; - PostMessage(FCallbackWnd, CEF_LOADINGSTATECHANGE, v, 0); -end; - -procedure TframeCEFHost.Handle_CEF_KEYEVENT(var message: TMessage); -var - p: PCEFHostKeyEventData; - wasHandled: Boolean; - wasShortcut: Boolean; -begin - p := PCEFHostKeyEventData(message.LParam); - - if p.event.windows_key_code = VK_F1 then - begin - if Assigned(FOnHelpTopic) then FOnHelpTopic(Self); // TODO: frmKeymanDeveloper.HelpTopic(Self) - end - else if p.event.windows_key_code = VK_F12 then - begin - cef.ShowDevTools(Point(Low(Integer),Low(Integer)), nil); - end - else if Assigned(FOnKeyEvent) then - begin - wasHandled := message.WParamLo <> 0; - wasShortcut := message.WParamHi <> 0; - FOnKeyEvent(Self, p^, wasShortcut, wasHandled); - end; - FreeMem(p); -end; - -procedure TframeCEFHost.Handle_CEF_LOADEND(var message: TMessage); -begin - if csDestroying in ComponentState then - Exit; - - // The focus needs to be set again for key events to - // be passed to the CEF window, even if it appears - // to already be focused to the expected window. - if IsChild(Handle, GetFocus) then - SetFocus; - - if Assigned(FOnLoadEnd) then - FOnLoadEnd(Self); -end; - -procedure TframeCEFHost.Handle_CEF_LOADINGSTATECHANGE(var message: TMessage); -begin - if csDestroying in ComponentState then - Exit; - AssertVclThread; - - if Assigned(FOnLoadingStateChange) then - FOnLoadingStateChange(Self, - (message.WParam and CEF_LOADINGSTATECHANGE_ISLOADING) <> 0, - (message.WParam and CEF_LOADINGSTATECHANGE_CANGOBACK) <> 0, - (message.WParam and CEF_LOADINGSTATECHANGE_CANGOFORWARD) <> 0); -end; - -procedure TframeCEFHost.Handle_CEF_SETFOCUS(var message: TMessage); -begin - AssertVclThread; - if Assigned(cefwp) and cefwp.Visible and cefwp.CanFocus then - GetParentForm(cefwp).ActiveControl := cefwp; -end; - -procedure TframeCEFHost.cefPreKeyEvent(Sender: TObject; - const browser: ICefBrowser; const event: PCefKeyEvent; osEvent: TCefEventHandle; - out isKeyboardShortcut, Result: Boolean); -var - p: PCEFHostKeyEventData; -begin - AssertCefThread; - Result := False; - - p := AllocMem(Sizeof(TCEFHostKeyEventData)); - p.browserid := browser.Identifier; - p.event := event^; - if Assigned(osEvent) then - p.osEvent := osEvent^; - - if event.kind in [TCefKeyEventType.KEYEVENT_KEYDOWN, TCefKeyEventType.KEYEVENT_RAWKEYDOWN] then - begin - if Assigned(FOnPreKeySyncEvent) then - begin - FOnPreKeySyncEvent(Self, p^, isKeyboardShortcut, Result); - end; - - if not Result then // only run this if the prekeysyncevent didn't swallow the keystroke - begin - if event.windows_key_code = VK_F1 then - begin - isKeyboardShortcut := True; - Result := True; - end - else if event.windows_key_code = VK_F12 then - begin - isKeyboardShortcut := True; - Result := True; - end - else if event.windows_key_code <> VK_CONTROL then - begin - if SendMessage(FApplicationHandle, CM_APPKEYDOWN, event.windows_key_code, 0) = 1 then - begin - isKeyboardShortcut := True; - Result := True; - end; - end; - end; - - PostMessage(FCallbackWnd, CEF_KEYEVENT, MAKELONG(WORD(Result), WORD(isKeyboardShortcut)), LPARAM(p)); - end; -end; - - -procedure TframeCEFHost.cefBeforePopup(Sender: TObject; - const browser: ICefBrowser; const frame: ICefFrame; const targetUrl, - targetFrameName: ustring; targetDisposition: TCefWindowOpenDisposition; - userGesture: Boolean; const popupFeatures: TCefPopupFeatures; - var windowInfo: TCefWindowInfo; var client: ICefClient; - var settings: TCefBrowserSettings; - var extra_info: ICefDictionaryValue; - var noJavascriptAccess, Result: Boolean); -begin - AssertCefThread; - DoBeforeBrowse(targetUrl, frame.IsMain, True, True, Result); -end; - -procedure TframeCEFHost.cefRunContextMenu(Sender: TObject; - const browser: ICefBrowser; const frame: ICefFrame; - const params: ICefContextMenuParams; const model: ICefMenuModel; - const callback: ICefRunContextMenuCallback; var aResult: Boolean); -begin - AssertCefThread; - // Return FALSE to show default context menu - aResult := not FShouldShowContextMenu and (GetKeyState(VK_SHIFT) >= 0); -end; - -procedure TframeCEFHost.cefSetFocus(Sender: TObject; const browser: ICefBrowser; - source: TCefFocusSource; out Result: Boolean); -begin - Result := source = FOCUS_SOURCE_NAVIGATION; -end; - -procedure TframeCEFHost.cefTitleChange(Sender: TObject; - const browser: ICefBrowser; const title: ustring); -var - p: PCEFTitleChangeEventData; -begin - AssertCefThread; - - p := AllocMem(SizeOf(TCEFTitleChangeEventData)); - p.browserid := browser.Identifier; - p.title := title; - - PostMessage(FCallbackWnd, CEF_TITLECHANGE, 0, LPARAM(p)); -end; - -procedure TframeCEFHost.WMEnterMenuLoop(var aMessage: TMessage); -begin - AssertVclThread; - inherited; - if (aMessage.wParam = 0) and (GlobalCEFApp <> nil) then GlobalCEFApp.OsmodalLoop := True; -end; - -procedure TframeCEFHost.WMExitMenuLoop(var aMessage: TMessage); -begin - AssertVclThread; - inherited; - if (aMessage.wParam = 0) and (GlobalCEFApp <> nil) then GlobalCEFApp.OsmodalLoop := False; -end; - -procedure TframeCEFHost.WMMove(var aMessage: TWMMove); -begin - AssertVclThread; - inherited; - if cef <> nil then cef.NotifyMoveOrResizeStarted; -end; - -procedure TframeCEFHost.WMMoving(var aMessage: TMessage); -begin - AssertVclThread; - inherited; - if cef <> nil then cef.NotifyMoveOrResizeStarted; -end; - -end. From cdc4c0b6b41c415e3dbbca8edfa6a273687be5bc Mon Sep 17 00:00:00 2001 From: kongchanlina Date: Mon, 23 Mar 2026 16:00:52 +0700 Subject: [PATCH 017/140] changed splash_screen logo --- .../com/tavultesoft/kmapro/MainActivity.java | 19 +++++++----------- .../main/res/drawable-hdpi/keyman_text.png | Bin 2683 -> 2637 bytes .../res/drawable/branded_logo_dark_mode.xml | 2 +- .../drawable/keyman_logo_splash_dark_mode.png | Bin 8222 -> 12905 bytes .../KMAPro/kMAPro/src/main/res/menu/main.xml | 15 ++++---------- .../src/main/res/values-night/colors.xml | 2 +- 6 files changed, 13 insertions(+), 25 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index a17b32253f..8b0ea603b1 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -80,14 +80,11 @@ import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import androidx.annotation.NonNull; import androidx.core.app.ActivityCompat; -<<<<<<< Updated upstream -======= -<<<<<<< Updated upstream + import androidx.drawerlayout.widget.DrawerLayout; -======= + import androidx.core.content.ContextCompat; ->>>>>>> Stashed changes ->>>>>>> Stashed changes + import android.util.TypedValue; import android.view.KeyEvent; @@ -175,21 +172,19 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene toolbar = (Toolbar) findViewById(R.id.titlebar); setSupportActionBar(toolbar); getSupportActionBar().setTitle(null); -<<<<<<< Updated upstream + getSupportActionBar().setDisplayUseLogoEnabled(true); getSupportActionBar().setDisplayShowHomeEnabled(true); getSupportActionBar().setLogo(R.drawable.keyman_logo); -======= -<<<<<<< Updated upstream + getSupportActionBar().setDisplayUseLogoEnabled(false); getSupportActionBar().setDisplayShowHomeEnabled(false); -======= + getSupportActionBar().setDisplayUseLogoEnabled(true); getSupportActionBar().setDisplayShowHomeEnabled(true); getSupportActionBar().setLogo(R.drawable.keyman_logo_mode); // getSupportActionBar().setLogo(R.drawable.keyman_logo); ->>>>>>> Stashed changes ->>>>>>> Stashed changes + getSupportActionBar().setDisplayShowTitleEnabled(false); getSupportActionBar().setBackgroundDrawable(getActionBarDrawable(this)); diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-hdpi/keyman_text.png b/android/KMAPro/kMAPro/src/main/res/drawable-hdpi/keyman_text.png index 2f2891cfb826a7a9845f041c797fcd50586a666d..48b555f559bfdaddd7aa2af87a5aeab11eb0923d 100644 GIT binary patch literal 2637 zcmV-T3bOTyP)IKRT=;N&Yhj^_BFeGu-loPduQ$vL9wN4oh|-z4_s-0Ace`}m zmVI<*=6wFq-R-?Iceh)*+mg^HkkFC zRbv^nwzkfxqw#%`5x=}N*neNXt1B~AL_f-i0P0i{H;hUIy#Q8)DbJrje`Zt}8%Qn+ zOlVnsI6+f~$i7uZ5uMdA;|UUh1PSEcY~Ect$-EB3hlwW1u#?GqNSx~zipJV(-u?Cz z@Hz}1)I^kQ(_BquTU2YJU+d8VxMnP+BB~r)eU}`h-}Py?gapJhzS#k#K)%@#KLxOe^$oOV#kPsa>XK9*EEEa@0AM$_9xv0>O(ZWM7!}jf zX~+wr^-8Buc(s~?8H+WkSxpO-(&s~PD#^44rYquo5xgakwKAYKINO~Lzg=5v>ye_= z-2rD>f2XfhYM&8pSV4sw0nD!#kv7wM1(otmnPTxp07BbL{fP7x3WDB@=QIu{=we0n zR8@lB)`}-Pjx$)jfD}^oY}I!vLw!B$ArIS&hFtA<-md|OowOdL;uXI6-mK>y0br=g zRN2XtLxtq%8b#lq^SrBU(|VKS38OwV)(9l86jz6F(2&22%P%NXUq`B2$Kt9*ezF zpCAztRpvwCF>v8n2?78Go<<9s$4d|Z5WFYV(vlh(_YDqi;rQG82LXMy z1B?dbf_sX^;(K9^=FFKhn^b`^T(qpsdhXnu=be;w-DCQ852gg@@;?A-y%GR)ML!rM z&t(7tTZ$sj3;2gT8pdS=TFdi2yLQgaxXv_+{u*kxPXH+CO8J_M>qPrJHzrzds8%5P zsld3R9~Uk6_7Cm0vz|LW<2td7>&zbNt5~9Ca~0=fta$u{F-eM)MQ~%rb*5!Jce-f3 zruG9RpQ%X_foTM0SId>+&BQPb)QVESe;1-dqiNu1ZObSAv%ycmc%OKF0BM z(tIqJ_m&4%2BZTkAy%|msTJe+9)%)p0GmZ{KY^t_9~S^vvBy0x3@n5QEg1F^;?8Q3 zR;uPKEe%PH9)vr`>81=jX>Fk5QfL8`0YE@yAfgPkS*aQWB{JrVFr;-&d?nHg1_^{Z z5D~2_<;9JjbFM&K{~XCn(x&<4D%VkbKxn8L0FW)XzZa1nU!=LI*4EQf^HVJ%a*B^! zxp&vDbssd|fxtGcSE#sj-_A=x)@L@vYZWinZL@^kl*dtv7`14m|~q?S=Gg*sXSBAuQ_V>-| z{$J0Wa;1D#@PjrvsM+oa?#yn>zcez=6$)Ey)4UPjZ9c6iqWWn7p9COU@RlM@-#g8X zS=ag8{*sYO^HNGDY`q zqV;Q|a#8_YXSbTaOw3Bm1%P-w{xRFM*0WCim4Zjot=11hBMBY|#PDx*f+qolHhohK zf$Rtf$mIb1XEEDM>pqZem}E@21kZJKb!uG+R%~R1b?T8=EY>KZuL_jcWCIQLy=hI6 zt~M*RyFOC)Z;}@Qh=Pnz;U}qh@`~y>LsD5Q;f*Jt%VYLNQx2{F;vW(`+O{mO&9XMx zX6jYjvWg(by%PJN*sngx$>-OKmf-CG*)liQ_*kw`*izD!GexAoYF+_|l2vu3;HgM^ z{)y@sg+gJ6h<>_SE+TK|3We9JX}5;uL}lM5JFt3X>jte+_mH z^j#|=)s5uRv}s5i(hRq)c*wWH6eRjJh<+0tVB1?7(VY&92a-n<2Mkx0|%xeouab^fVP^O({*ISi0*)4bQX)n_fkmJM_L90@waw# zWB~-?n=!K~+Ay?WbS;_~rg)6h3d&i;Xcc}4y0Pbt79ZyWg00000NkvXXu0mjfn%4Mt literal 2683 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 diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/branded_logo_dark_mode.xml b/android/KMAPro/kMAPro/src/main/res/drawable/branded_logo_dark_mode.xml index 9d467ae156..5cb427161f 100644 --- a/android/KMAPro/kMAPro/src/main/res/drawable/branded_logo_dark_mode.xml +++ b/android/KMAPro/kMAPro/src/main/res/drawable/branded_logo_dark_mode.xml @@ -10,7 +10,7 @@ android:bottom="@dimen/product_logo_bottom" > diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/keyman_logo_splash_dark_mode.png b/android/KMAPro/kMAPro/src/main/res/drawable/keyman_logo_splash_dark_mode.png index f26f30c78647e1e3459361ea6532b80f489cb121..0ed8dd4e0d6df94498352cd09c2b6d31e38ab7ce 100644 GIT binary patch literal 12905 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%0A8&a!4Z?nlaikl{YT(`@Yve zQw-vz$(lRhwM)yl4K{ajl?Wl=s+O>U*W5n#r~vVTwR8!tmH`k!NkoAYgX6w*B4= z?R-CTI0q{!gfUyTSK7xt<`9RzmEy@QwauUP?5^je0YD$=fKkJBG_mAKj%HcXmMlW| zlU3(@knoi}zKuo&P2J^Q8$&PgsW1WXLZz2lsUqU-dJPfwO`KP%c-THI9fNa3$i1ll|KKtKPyxWrZ=D%dXZS{^sg+xY*#S^kyp!-xuJHDGR@M@Y;Qzqk27+> z0bV*YzqRW_wVpXxsj>_P9Gd0Ny?f>ANqgW&r!U>LH!kJFH(<(r=1@bxp>S+G+uKBE zW)+w;R(7w_bED26*Ggr_QuH?o+mJc;~-uduqiJCY|n^!xSa(nE5}nvAq&Fd8$UjO|{8xX<(PG z_=kgz2VOTKWmjan%L4>6q8F>NAaR8Q=C83X`74P zUpnONWYBaU-F#2{`KNQDea#U?J9wepn>Gl-cJ|&!*qX=bC`3hu%@=y!AKY?)sbMv-`)j&NERHltv^X9Y4(LcRd-u%N?At{7`$XmU%+=+wkkZSF)`7j_2dq&a=6KvfaO3|GEKVKiOPHw^>i@N8hFe(t4}JX@RB3J9@o049shOY zCg-~HDusKP8$vx;{&-F_ZYd~9X6|W6!8QZT}LAlms?~HJ@Sk5E zR|~J16;5MAUi=#Nc{*Vf#!H#?RqlJB$UsfVE7<2()@RejC#$&>c)z;zu9u@=9`J@l z^P7nma+XRZo|d#BVefM$8ZQ;Mp@E56UZ^|VRg7&YAbGl&0&U_icT6}`w~G0viV5OLw^MuOrB1J15*@0kt=LUAc|x%Q^n2hO;IG${N~l4;UD z9o2dq6=PLhs*$-oE2!D85YyyG`R$pfw=vP}qE~i7k6GV--S3RNbJg_!4KdpJ(ng|5U=@P+KhOpWu&a2=B|wA#%$DLOq^}drROJh0t4Nn=@N#6&;DOh#2 zw*k!zf#Z;Y1Yi16Gi9WWv&dE8Ef${5{N%XBamT;uW~d`yJRv7+i;|H7qVN2w7$2B8 zz(i$g_KYsTD-*Qi;gz)sR;IkFP8l05zFT>xPvwYS&6w$gUV>*X;4azEZTpg96xECe ze9Azd6zJG3iB9S`QZ*D*;E%6}kuUD_TY;y`O;bv|l|w?4nEg)$8pp}afWYHi;+z{h z0jg7FU`5h+N;|lX|JH0>uv}i9^7|qK<#k|Z!9TG_4MX6`W7UfW`IzAPUcJS3&cpwe zjUFsoh*3%h44SQz-p#gDv>cNrr79(ApG*rReQxnI6hewSx2Bw%`S)$mg}a!2G2Pf< zxmnn1(nn+`t8Z8$a*3Nc>oeaxtV7KsxnTZSZcKYw33Oa3!8ea=t$3fj*p^JGsWF8O zpCLS0rK|m}y7QQvf4i{w5 zsC}ufh+iYiE^Wz4IA*WiTY+580>iqZHQ=~@aSQuufdk5h!9V@~7bByL$~ogC+n8h>R$tZ}S7n#6d3eS5ZCRU^lB|_`9lO4P5j^qmWmn*Dw)CKN zGqOHJ^u>uUsSNmJ7;k^~*R}^^{YYKK@u1!f2+e78zGRW;D0@z;pK_`pm>l?o8Xl_k zU9IGHW@n{ibx9(e@mlRuzmwTbum_9y2YPWChK)~2z4|hVU{Ke5$JT|HtipjWD^;sW zQfu-?)a|=qo?iG5SKmRCDD!>Qep9cP}~7%=sjyYKPaCGtW>sonrr)(gAXO5=g0 zzs^rF#uRv^WMBZZekT{#4h4hgAFubEuJwA`jww`4XtE~9Jje{9f>5Dh%%q{JE=lqm zvSV$%qiZQ37g#pBu4!SJR{LZs{Z^` z534`I=FJl>F#~etdB#Jg4FO2#2OPA(%Z&~IX^XP0n$&@>L|-Op!yl&q>fry2!2c@* dY9A<8BPyGntF2T1{T;#5RMl0fe)Tr|e*pREYn%W8 literal 8222 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 + + + + -<<<<<<< Updated upstream - -<<<<<<< Updated upstream -======= - -======= ->>>>>>> Stashed changes ->>>>>>> Stashed changes - #171717 + #131313 #FFFFFF @color/ms_black From 92c795c60f01c116cc2bf787c8ea26032de70c22 Mon Sep 17 00:00:00 2001 From: kongchanlina Date: Mon, 23 Mar 2026 16:21:49 +0700 Subject: [PATCH 018/140] dark-mode-on-side-panel --- .../src/main/java/com/tavultesoft/kmapro/MainActivity.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index 726e6db829..dc7dd49040 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -49,6 +49,7 @@ import com.keyman.engine.util.WebViewUtils.SystemWebViewStatus; import android.app.ProgressDialog; import android.content.pm.PackageManager; +import android.graphics.Color; import android.net.Uri; import android.net.Uri.Builder; import android.os.Build; @@ -94,6 +95,7 @@ import androidx.drawerlayout.widget.DrawerLayout; import androidx.core.content.ContextCompat; +import android.provider.Settings; import android.util.TypedValue; import android.view.KeyEvent; import android.view.LayoutInflater; From dc70f6854d901f8d3d4ee4b6010e0ab23c3b924c Mon Sep 17 00:00:00 2001 From: Mengchou97 Date: Mon, 23 Mar 2026 22:38:36 +0700 Subject: [PATCH 019/140] fixed the conflict sidebr button --- .../kmapro/KeymanSettingsActivity.java | 6 +++ .../com/tavultesoft/kmapro/MainActivity.java | 43 +++++++++++++++++-- .../src/main/res/drawable/ic_hamburger.xml | 22 ++++++++++ .../src/main/res/drawable/ic_more_vert_24.xml | 10 +++++ .../src/main/res/drawable/spacebar_key_bg.xml | 17 ++++++++ .../res/layout/nav_item_with_subtitle.xml | 25 +++++++++++ .../KMAPro/kMAPro/src/main/res/menu/main.xml | 17 +++++--- .../kMAPro/src/main/res/menu/nav_menu.xml | 16 +++++-- .../kMAPro/src/main/res/values/strings.xml | 1 + 9 files changed, 146 insertions(+), 11 deletions(-) create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable/ic_hamburger.xml create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable/ic_more_vert_24.xml create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable/spacebar_key_bg.xml create mode 100644 android/KMAPro/kMAPro/src/main/res/layout/nav_item_with_subtitle.xml diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsActivity.java index bda56856b2..fecbc6cddb 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsActivity.java @@ -38,6 +38,12 @@ public class KeymanSettingsActivity extends BaseActivity { innerFragment = (KeymanSettingsFragment) getSupportFragmentManager().findFragmentById(R.id.keyman_settings_fragment); + // Scroll to a specific preference if requested (e.g. from sidebar) + String scrollToKey = getIntent().getStringExtra("scrollToPreference"); + if (scrollToKey != null) { + innerFragment.scrollToPreference(scrollToKey); + } + // For Keyman sites, disable keyboard picker task flag so keyboard picker doesn't dismiss Keyman app KMManager.dontCloseParentAppOnShowKeyboardPicker(); } diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index c7811e07a1..4fbefca621 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -249,6 +249,44 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } else if (id == R.id.nav_longpress_delay) { drawerLayout.closeDrawers(); startActivity(new Intent(context, AdjustLongpressDelayActivity.class)); + } else if (id == R.id.nav_spacebar_caption) { + drawerLayout.closeDrawers(); + + // Same entries as in KeymanSettingsFragment + CharSequence[] entries = { + getString(R.string.spacebar_caption_language), + getString(R.string.spacebar_caption_keyboard), + getString(R.string.spacebar_caption_language_keyboard), + getString(R.string.spacebar_caption_blank) + }; + + KMManager.SpacebarText[] values = { + KMManager.SpacebarText.LANGUAGE, + KMManager.SpacebarText.KEYBOARD, + KMManager.SpacebarText.LANGUAGE_KEYBOARD, + KMManager.SpacebarText.BLANK + }; + + // Find the currently selected index + KMManager.SpacebarText current = KMManager.getSpacebarText(); + int currentIndex = Arrays.asList(values).indexOf(current); + + new AlertDialog.Builder(context) + .setTitle(getString(R.string.spacebar_caption)) + .setSingleChoiceItems(entries, currentIndex, (dialog, which) -> { + KMManager.setSpacebarText(values[which]); + + // Persist the selection just like KeymanSettingsFragment does + SharedPreferences prefs = getSharedPreferences( + getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); + prefs.edit() + .putString(KeymanSettingsActivity.spacebarTextKey, values[which].toString()) + .apply(); + + dialog.dismiss(); + }) + .setNegativeButton(getString(R.string.label_cancel), null) + .show(); } else if (id == R.id.nav_settings) { drawerLayout.closeDrawers(); startActivity(new Intent(context, KeymanSettingsActivity.class)); @@ -583,7 +621,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } return true; - } else if (item.getItemId() == R.id.action_overflow) { + } else if (item.getItemId() == R.id.action_sidebar) { // Open the drawer from the end when the hamburger (overflow) icon is clicked if (drawerLayout.isDrawerOpen(GravityCompat.END)) { drawerLayout.closeDrawer(GravityCompat.END); @@ -1106,8 +1144,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene bindDrawerSwitch(navigationView, R.id.nav_toggle_haptic_feedback, prefs.getBoolean(KeymanSettingsActivity.hapticFeedbackKey, false), new SwitchChangeHandler() { - @Override - public void onChanged(boolean isChecked) { + @Override public void onChanged(boolean isChecked) { SharedPreferences.Editor editor = prefs.edit(); editor.putBoolean(KeymanSettingsActivity.hapticFeedbackKey, isChecked); editor.apply(); diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_hamburger.xml b/android/KMAPro/kMAPro/src/main/res/drawable/ic_hamburger.xml new file mode 100644 index 0000000000..73c78665b0 --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/drawable/ic_hamburger.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_more_vert_24.xml b/android/KMAPro/kMAPro/src/main/res/drawable/ic_more_vert_24.xml new file mode 100644 index 0000000000..f79e4779fa --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/drawable/ic_more_vert_24.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/spacebar_key_bg.xml b/android/KMAPro/kMAPro/src/main/res/drawable/spacebar_key_bg.xml new file mode 100644 index 0000000000..0b55d9e593 --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/drawable/spacebar_key_bg.xml @@ -0,0 +1,17 @@ + + + + + + + + + \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/res/layout/nav_item_with_subtitle.xml b/android/KMAPro/kMAPro/src/main/res/layout/nav_item_with_subtitle.xml new file mode 100644 index 0000000000..982f98d805 --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/layout/nav_item_with_subtitle.xml @@ -0,0 +1,25 @@ + + + + + + + + \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/res/menu/main.xml b/android/KMAPro/kMAPro/src/main/res/menu/main.xml index 631af2970a..835228defe 100644 --- a/android/KMAPro/kMAPro/src/main/res/menu/main.xml +++ b/android/KMAPro/kMAPro/src/main/res/menu/main.xml @@ -20,7 +20,6 @@ - - + + + + + \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/res/menu/nav_menu.xml b/android/KMAPro/kMAPro/src/main/res/menu/nav_menu.xml index 76b7cfee8d..fecef17a39 100644 --- a/android/KMAPro/kMAPro/src/main/res/menu/nav_menu.xml +++ b/android/KMAPro/kMAPro/src/main/res/menu/nav_menu.xml @@ -1,6 +1,8 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + tools:context=".MainActivity"> @@ -8,7 +10,10 @@ - + + Open navigation drawer Close navigation drawer + Show sidebar menu Web Browser From 5f377c73d463d8b436f46e329d9fca632cb05f81 Mon Sep 17 00:00:00 2001 From: Mengchou97 Date: Wed, 25 Mar 2026 11:18:16 +0700 Subject: [PATCH 020/140] added subtitle --- .../com/tavultesoft/kmapro/MainActivity.java | 58 +++++++++++++++++++ .../src/main/res/layout/activity_main.xml | 1 + .../kMAPro/src/main/res/menu/nav_menu.xml | 10 ++-- .../kMAPro/src/main/res/values/strings.xml | 15 +++++ .../kMAPro/src/main/res/values/styles.xml | 6 ++ 5 files changed, 85 insertions(+), 5 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index 4fbefca621..1dc4796522 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -93,6 +93,9 @@ import androidx.drawerlayout.widget.DrawerLayout; import android.provider.Settings; import android.text.Html; +import android.text.SpannableString; +import android.text.Spanned; +import android.text.style.RelativeSizeSpan; import android.util.Log; import android.util.TypedValue; import android.view.KeyEvent; @@ -217,6 +220,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene drawerToggle.syncState(); navigationView = findViewById(R.id.nav_view); + navigationView.setItemMaxLines(2); navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() { @Override public boolean onNavigationItemSelected(@NonNull MenuItem item) { @@ -297,6 +301,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene return true; } }); + initializeDrawerItemSubtitles(navigationView); initializeDrawerToggleOptions(navigationView); initializeDrawerCheckboxOptions(navigationView); refreshDrawerSystemKeyboardCheckboxes(navigationView); @@ -1165,6 +1170,59 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene }); } + private void initializeDrawerItemSubtitles(NavigationView navigationView) { +// setDrawerItemSubtitle(navigationView, R.id.nav_installed_languages, +// getString(R.string.drawer_subtitle_installed_languages)); +// setDrawerItemSubtitle(navigationView, R.id.nav_install_keyboard, +// getString(R.string.drawer_subtitle_install_keyboard)); +// setDrawerItemSubtitle(navigationView, R.id.nav_checkbox_enable_system_keyboard, +// getString(R.string.drawer_subtitle_enable_system_keyboard)); +// setDrawerItemSubtitle(navigationView, R.id.nav_checkbox_set_default_keyboard, +// getString(R.string.drawer_subtitle_set_default_keyboard)); +// setDrawerItemSubtitle(navigationView, R.id.nav_display_language, +// getString(R.string.drawer_subtitle_display_language)); +// setDrawerItemSubtitle(navigationView, R.id.nav_keyboard_height, +// getString(R.string.drawer_subtitle_keyboard_height)); +// setDrawerItemSubtitle(navigationView, R.id.nav_longpress_delay, +// getString(R.string.drawer_subtitle_longpress_delay)); + setDrawerItemSubtitle(navigationView, R.id.nav_spacebar_caption, + getString(R.string.drawer_subtitle_spacebar_caption)); + setDrawerItemSubtitle(navigationView, R.id.nav_toggle_show_osk, + getString(R.string.drawer_subtitle_show_osk)); +// setDrawerItemSubtitle(navigationView, R.id.nav_toggle_haptic_feedback, +// getString(R.string.drawer_subtitle_haptic_feedback)); + setDrawerItemSubtitle(navigationView, R.id.nav_toggle_send_crash_report, + getString(R.string.drawer_subtitle_send_crash_report)); +// setDrawerItemSubtitle(navigationView, R.id.nav_settings, +// getString(R.string.drawer_subtitle_more_settings)); + setDrawerItemSubtitle(navigationView, R.id.nav_about, + getString(R.string.drawer_subtitle_about)); + } + + private void setDrawerItemSubtitle(NavigationView navigationView, int menuItemId, String subtitle) { + MenuItem menuItem = navigationView.getMenu().findItem(menuItemId); + if (menuItem == null || subtitle == null || subtitle.isEmpty()) { + return; + } + + CharSequence currentTitle = menuItem.getTitle(); + if (currentTitle == null || currentTitle.length() == 0) { + return; + } + + String baseTitle = currentTitle.toString(); + int newlineIndex = baseTitle.indexOf('\n'); + if (newlineIndex >= 0) { + baseTitle = baseTitle.substring(0, newlineIndex); + } + + SpannableString fullTitle = new SpannableString(baseTitle + "\n" + subtitle); + int subtitleStart = baseTitle.length() + 1; + fullTitle.setSpan(new RelativeSizeSpan(0.85f), subtitleStart, fullTitle.length(), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + menuItem.setTitle(fullTitle); + } + private void initializeDrawerCheckboxOptions(NavigationView navigationView) { bindDrawerCheckboxAction(navigationView, R.id.nav_checkbox_enable_system_keyboard, new Runnable() { diff --git a/android/KMAPro/kMAPro/src/main/res/layout/activity_main.xml b/android/KMAPro/kMAPro/src/main/res/layout/activity_main.xml index e31fa00e0c..561b7128d4 100644 --- a/android/KMAPro/kMAPro/src/main/res/layout/activity_main.xml +++ b/android/KMAPro/kMAPro/src/main/res/layout/activity_main.xml @@ -56,6 +56,7 @@ android:layout_gravity="end" android:paddingHorizontal="10dp" android:fitsSystemWindows="true" + app:itemTextAppearance="@style/NavigationDrawerItemText" app:headerLayout="@layout/nav_header" app:menu="@menu/nav_menu" /> diff --git a/android/KMAPro/kMAPro/src/main/res/menu/nav_menu.xml b/android/KMAPro/kMAPro/src/main/res/menu/nav_menu.xml index fecef17a39..96d887d566 100644 --- a/android/KMAPro/kMAPro/src/main/res/menu/nav_menu.xml +++ b/android/KMAPro/kMAPro/src/main/res/menu/nav_menu.xml @@ -28,12 +28,12 @@ @@ -67,9 +67,9 @@ android:title="Show On Screen Keyboard" app:actionLayout="@layout/nav_switch_item" /> Web Browser + + + + + + + + + Show keyboard name on spacebar + When a physical keyboard is connected + + When on, crash reports will be sent + + Version and app information + Text Size diff --git a/android/KMAPro/kMAPro/src/main/res/values/styles.xml b/android/KMAPro/kMAPro/src/main/res/values/styles.xml index ae386e0262..0d42fa70a4 100644 --- a/android/KMAPro/kMAPro/src/main/res/values/styles.xml +++ b/android/KMAPro/kMAPro/src/main/res/values/styles.xml @@ -38,4 +38,10 @@ @android:color/black + + From 7b0ec6e2b6e5c7692f0f1a3746abb173b9dbd0cc Mon Sep 17 00:00:00 2001 From: Mengchou97 Date: Wed, 25 Mar 2026 14:29:11 +0700 Subject: [PATCH 021/140] updated sidebar with pr --- .../com/tavultesoft/kmapro/MainActivity.java | 122 ++++++++++++++++-- .../src/main/res/layout/nav_switch_item.xml | 2 +- .../kMAPro/src/main/res/menu/nav_menu.xml | 17 ++- .../kMAPro/src/main/res/values/strings.xml | 5 +- 4 files changed, 130 insertions(+), 16 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index 1dc4796522..ac4d5e693c 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -95,6 +95,7 @@ import android.provider.Settings; import android.text.Html; import android.text.SpannableString; import android.text.Spanned; +import android.text.style.ForegroundColorSpan; import android.text.style.RelativeSizeSpan; import android.util.Log; import android.util.TypedValue; @@ -118,6 +119,7 @@ import android.view.inputmethod.InputMethodManager; import androidx.drawerlayout.widget.DrawerLayout; import androidx.appcompat.app.ActionBarDrawerToggle; import com.google.android.material.navigation.NavigationView; +import androidx.core.content.ContextCompat; import androidx.core.view.GravityCompat; import android.view.MenuItem; import androidx.appcompat.widget.AppCompatCheckBox; @@ -148,6 +150,10 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene private static final String userTextSizeKey = "UserTextSize"; private Toolbar toolbar; private Menu menu; + private String lastCurrentKeyboardDrawerTitle = null; + private String lastKnownKeyboardId = null; + private static final boolean DEBUG_CURRENT_KEYBOARD_LOGS = false; + private static final boolean SHOW_CURRENT_KEYBOARD_ERROR_TOAST = false; private static Dataset repo; private boolean didExecuteParser = false; @@ -225,7 +231,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene @Override public boolean onNavigationItemSelected(@NonNull MenuItem item) { int id = item.getItemId(); - if (id == R.id.nav_toggle_show_osk || id == R.id.nav_toggle_haptic_feedback || id == R.id.nav_toggle_send_crash_report) { + if (id == R.id.nav_toggle_show_osk || id == R.id.action_get_started || id == R.id.nav_toggle_send_crash_report) { toggleDrawerSwitch(navigationView, id); } else if (id == R.id.nav_checkbox_enable_system_keyboard) { drawerLayout.closeDrawers(); @@ -294,13 +300,18 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } else if (id == R.id.nav_settings) { drawerLayout.closeDrawers(); startActivity(new Intent(context, KeymanSettingsActivity.class)); - } else if (id == R.id.nav_about) { + } else if (id == R.id.nav_help + ) { drawerLayout.closeDrawers(); startActivity(new Intent(context, InfoActivity.class)); + } else if (id == R.id.nav_about_current_keyboard) { + drawerLayout.closeDrawers(); + showCurrentKeyboardSettings(); } return true; } }); + updateCurrentKeyboardDrawerItemTitle(navigationView); initializeDrawerItemSubtitles(navigationView); initializeDrawerToggleOptions(navigationView); initializeDrawerCheckboxOptions(navigationView); @@ -315,7 +326,9 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } @Override public void onDrawerOpened(View drawerView) { - + if (navigationView != null) { + updateCurrentKeyboardDrawerItemTitle(navigationView); + } } @Override @@ -661,7 +674,10 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene @Override public void onKeyboardChanged(String newKeyboard) { - textView.setTypeface(KMManager.getKeyboardTextFontTypeface(this)); + if (newKeyboard != null && !newKeyboard.isEmpty()) { + lastKnownKeyboardId = newKeyboard; + textView.setTypeface(KMManager.getKeyboardTextFontTypeface(this)); + } } @Override @@ -1146,14 +1162,14 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } }); - bindDrawerSwitch(navigationView, R.id.nav_toggle_haptic_feedback, - prefs.getBoolean(KeymanSettingsActivity.hapticFeedbackKey, false), + bindDrawerSwitch(navigationView, R.id.action_get_started, + prefs.getBoolean(GetStartedActivity.showGetStartedKey, true), new SwitchChangeHandler() { - @Override public void onChanged(boolean isChecked) { + @Override + public void onChanged(boolean isChecked) { SharedPreferences.Editor editor = prefs.edit(); - editor.putBoolean(KeymanSettingsActivity.hapticFeedbackKey, isChecked); + editor.putBoolean(GetStartedActivity.showGetStartedKey, isChecked); editor.apply(); - KMManager.setHapticFeedback(isChecked); } }); @@ -1195,8 +1211,80 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene getString(R.string.drawer_subtitle_send_crash_report)); // setDrawerItemSubtitle(navigationView, R.id.nav_settings, // getString(R.string.drawer_subtitle_more_settings)); - setDrawerItemSubtitle(navigationView, R.id.nav_about, + setDrawerItemSubtitle(navigationView, R.id.nav_help, getString(R.string.drawer_subtitle_about)); + setDrawerItemSubtitle(navigationView, R.id.nav_about_current_keyboard, + getString(R.string.drawer_subtitle_about_current_keyboard)); + } + + private void updateCurrentKeyboardDrawerItemTitle(NavigationView navigationView) { + MenuItem menuItem = navigationView.getMenu().findItem(R.id.nav_about_current_keyboard); + if (menuItem == null) { + return; + } + + Keyboard currentKeyboard = getBestAvailableKeyboard(); + String newTitle; + if (currentKeyboard != null && currentKeyboard.getKeyboardName() != null && !currentKeyboard.getKeyboardName().isEmpty()) { + newTitle = getString(R.string.drawer_about_current_keyboard) + ": " + currentKeyboard.getKeyboardName(); + } else { + newTitle = getString(R.string.drawer_about_current_keyboard) + ": " + getString(R.string.drawer_about_no_active_keyboard); + } + + if (!newTitle.equals(lastCurrentKeyboardDrawerTitle)) { + menuItem.setTitle(newTitle); + lastCurrentKeyboardDrawerTitle = newTitle; + } + } + + private void showCurrentKeyboardSettings() { + Keyboard currentKeyboard = getBestAvailableKeyboard(); + if (currentKeyboard == null) { + if (BuildConfig.DEBUG && DEBUG_CURRENT_KEYBOARD_LOGS) { + KMLog.LogInfo(TAG, "Current keyboard unavailable; not opening KeyboardSettingsActivity."); + } + if (SHOW_CURRENT_KEYBOARD_ERROR_TOAST) { + Toast.makeText(this, getString(R.string.drawer_about_no_active_keyboard), Toast.LENGTH_SHORT).show(); + } + return; + } + + Intent intent = new Intent(this, KeyboardSettingsActivity.class); + Bundle bundle = new Bundle(); + bundle.putSerializable(KMManager.KMKey_Keyboard, currentKeyboard); + intent.putExtras(bundle); + startActivity(intent); + } + + private Keyboard getBestAvailableKeyboard() { + List keyboards = KMManager.getKeyboardsList(this); + if (keyboards == null || keyboards.isEmpty()) { + if (BuildConfig.DEBUG && DEBUG_CURRENT_KEYBOARD_LOGS) { + KMLog.LogInfo(TAG, "No installed keyboards available."); + } + return null; + } + + if (keyboards.size() == 1) { + return keyboards.get(0); + } + + if (lastKnownKeyboardId != null && !lastKnownKeyboardId.isEmpty()) { + for (Keyboard keyboard : keyboards) { + String keyboardId = keyboard.getKeyboardID(); + if (keyboardId != null && (keyboardId.equals(lastKnownKeyboardId) + || lastKnownKeyboardId.contains(keyboardId) + || keyboardId.contains(lastKnownKeyboardId))) { + return keyboard; + } + } + } + + if (BuildConfig.DEBUG && DEBUG_CURRENT_KEYBOARD_LOGS) { + KMLog.LogInfo(TAG, "Current keyboard id unavailable; falling back to first installed keyboard."); + } + + return keyboards.get(0); } private void setDrawerItemSubtitle(NavigationView navigationView, int menuItemId, String subtitle) { @@ -1220,6 +1308,15 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene int subtitleStart = baseTitle.length() + 1; fullTitle.setSpan(new RelativeSizeSpan(0.85f), subtitleStart, fullTitle.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(android.R.attr.textColorSecondary, typedValue, true); + int subtitleColor = typedValue.resourceId != 0 + ? ContextCompat.getColor(this, typedValue.resourceId) + : typedValue.data; + fullTitle.setSpan(new ForegroundColorSpan(subtitleColor), subtitleStart, fullTitle.length(), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + menuItem.setTitle(fullTitle); } @@ -1276,9 +1373,12 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene return; } + switchView.setClickable(false); + switchView.setFocusable(false); + switchView.setLongClickable(false); + switchView.setChecked(defaultValue); switchView.setOnCheckedChangeListener((buttonView, isChecked) -> switchChangeHandler.onChanged(isChecked)); - actionView.setOnClickListener(v -> switchView.toggle()); } private void toggleDrawerSwitch(NavigationView navigationView, int menuItemId) { diff --git a/android/KMAPro/kMAPro/src/main/res/layout/nav_switch_item.xml b/android/KMAPro/kMAPro/src/main/res/layout/nav_switch_item.xml index af00b3fe78..db85b1f0e4 100644 --- a/android/KMAPro/kMAPro/src/main/res/layout/nav_switch_item.xml +++ b/android/KMAPro/kMAPro/src/main/res/layout/nav_switch_item.xml @@ -5,6 +5,6 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:focusable="false" - android:clickable="true" + android:clickable="false" android:minWidth="48dp" android:minHeight="48dp" /> diff --git a/android/KMAPro/kMAPro/src/main/res/menu/nav_menu.xml b/android/KMAPro/kMAPro/src/main/res/menu/nav_menu.xml index 96d887d566..6dee56586d 100644 --- a/android/KMAPro/kMAPro/src/main/res/menu/nav_menu.xml +++ b/android/KMAPro/kMAPro/src/main/res/menu/nav_menu.xml @@ -16,6 +16,10 @@ > + - + + + + + + + + android:title="User guide" /> + diff --git a/android/KMAPro/kMAPro/src/main/res/values/strings.xml b/android/KMAPro/kMAPro/src/main/res/values/strings.xml index d46a4c4d75..f7bb70834e 100644 --- a/android/KMAPro/kMAPro/src/main/res/values/strings.xml +++ b/android/KMAPro/kMAPro/src/main/res/values/strings.xml @@ -26,7 +26,10 @@ When on, crash reports will be sent - Version and app information + Learn how to use the keyboard + Help and QR code for the active keyboard + Current keyboard + No active keyboard Text Size From 9f7d1b1dd398da53390be697cd0d747c069f97ba Mon Sep 17 00:00:00 2001 From: Mengchou97 Date: Sun, 29 Mar 2026 21:01:17 +0700 Subject: [PATCH 022/140] fixed sidebar layout and remove redundant code --- .../res/layout/nav_item_with_subtitle.xml | 25 ------------------- 1 file changed, 25 deletions(-) delete mode 100644 android/KMAPro/kMAPro/src/main/res/layout/nav_item_with_subtitle.xml diff --git a/android/KMAPro/kMAPro/src/main/res/layout/nav_item_with_subtitle.xml b/android/KMAPro/kMAPro/src/main/res/layout/nav_item_with_subtitle.xml deleted file mode 100644 index 982f98d805..0000000000 --- a/android/KMAPro/kMAPro/src/main/res/layout/nav_item_with_subtitle.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - \ No newline at end of file From 25617f88b7eb742153c4ef7ad42d458dc2958c58 Mon Sep 17 00:00:00 2001 From: Mengchou97 Date: Mon, 30 Mar 2026 15:52:29 +0700 Subject: [PATCH 023/140] added the floating button for toolbar and final touches on icon in the sidebar --- .../com/tavultesoft/kmapro/MainActivity.java | 158 ++++++++++++++++-- .../bg_keyboard_toolbar_container.xml | 6 + .../drawable/bg_keyboard_toolbar_toggle.xml | 5 + .../main/res/drawable/ic_arrow_dropdown.xml | 12 ++ .../res/drawable/ic_keyboard_on_screen.xml | 123 ++++++++++++++ .../res/drawable/ic_keyboard_physical.xml | 130 ++++++++++++++ .../src/main/res/drawable/ic_warning.xml | 32 ++++ .../src/main/res/layout/activity_main.xml | 62 +++++++ .../src/main/res/layout/nav_checkbox_item.xml | 24 ++- .../main/res/layout/nav_right_icon_item.xml | 22 +++ .../KMAPro/kMAPro/src/main/res/menu/main.xml | 86 +++++----- .../kMAPro/src/main/res/menu/nav_menu.xml | 41 +++-- 12 files changed, 620 insertions(+), 81 deletions(-) create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable/bg_keyboard_toolbar_container.xml create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable/bg_keyboard_toolbar_toggle.xml create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable/ic_arrow_dropdown.xml create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable/ic_keyboard_on_screen.xml create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable/ic_keyboard_physical.xml create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable/ic_warning.xml create mode 100644 android/KMAPro/kMAPro/src/main/res/layout/nav_right_icon_item.xml diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index ac4d5e693c..4cc1451568 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -103,6 +103,7 @@ import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; +import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; @@ -142,6 +143,9 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene private ConstraintLayout constraintLayout; private KMTextView textView; + private View keyboardToolbarToggleButton; + private View keyboardToolbarContainer; + private boolean isKeyboardToolbarExpanded = false; private final int minTextSize = 16; private final int maxTextSize = 72; private int textSize = minTextSize; @@ -297,9 +301,9 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene }) .setNegativeButton(getString(R.string.label_cancel), null) .show(); - } else if (id == R.id.nav_settings) { - drawerLayout.closeDrawers(); - startActivity(new Intent(context, KeymanSettingsActivity.class)); +// } else if (id == R.id.nav_settings) { +// drawerLayout.closeDrawers(); +// startActivity(new Intent(context, KeymanSettingsActivity.class)); } else if (id == R.id.nav_help ) { drawerLayout.closeDrawers(); @@ -347,6 +351,8 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene textView.setTextSize((float) textSize); textView.setSelection(textView.getText().length()); + initializeKeyboardToolbar(); + // Check WebView enabled and Chrome version SystemWebViewStatus webViewStatus = WebViewUtils.getSystemWebViewStatus(context); EngineWebViewVersionStatus chromeStatus = KMManager.getEngineWebViewVersionStatus(); @@ -530,26 +536,26 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } @SuppressLint("RestrictedApi") - @Override - public boolean onPrepareOptionsMenu(final Menu menu) { - this.menu = menu; - final MenuItem _overflowMenuItem = menu.findItem(R.id.action_overflow); - if (_overflowMenuItem != null) { - MenuItem updateKeyboards = this.menu.findItem(R.id.action_update_keyboards); - updateUpdateCountIndicator(updateKeyboards, - KMManager.getUpdateTool().getOpenUpdateCount(), true); - } - return super.onPrepareOptionsMenu(menu); - } +// @Override +// public boolean onPrepareOptionsMenu(final Menu menu) { +// this.menu = menu; +// final MenuItem _overflowMenuItem = menu.findItem(R.id.action_overflow); +// if (_overflowMenuItem != null) { +// MenuItem updateKeyboards = this.menu.findItem(R.id.action_update_keyboards); +// updateUpdateCountIndicator(updateKeyboards, +// KMManager.getUpdateTool().getOpenUpdateCount(), true); +// } +// return super.onPrepareOptionsMenu(menu); +// } private void updateUpdateCountIndicator(int anUpdateCount) { if (menu == null) { return; } - final MenuItem _overflowMenuItem = menu.findItem(R.id.action_overflow); - if (_overflowMenuItem != null) { - updateUpdateCountIndicator(_overflowMenuItem, anUpdateCount, false); - } +// final MenuItem _overflowMenuItem = menu.findItem(R.id.action_overflow); +// if (_overflowMenuItem != null) { +// updateUpdateCountIndicator(_overflowMenuItem, anUpdateCount, false); +// } final MenuItem _keyboardupdate = menu.findItem(R.id.action_update_keyboards); if (_keyboardupdate != null && anUpdateCount > 0) { @@ -667,6 +673,34 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene return super.onKeyUp(keycode, e); } + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_DOWN && isKeyboardToolbarExpanded) { + boolean touchOnToolbar = isTouchInsideView(ev, keyboardToolbarContainer); + boolean touchOnToggle = isTouchInsideView(ev, keyboardToolbarToggleButton); + if (!touchOnToolbar && !touchOnToggle) { + setKeyboardToolbarExpanded(false, true); + } + } + return super.dispatchTouchEvent(ev); + } + + private boolean isTouchInsideView(MotionEvent ev, View view) { + if (view == null || view.getVisibility() != View.VISIBLE) { + return false; + } + + int[] location = new int[2]; + view.getLocationOnScreen(location); + float x = ev.getRawX(); + float y = ev.getRawY(); + + return x >= location[0] + && x <= location[0] + view.getWidth() + && y >= location[1] + && y <= location[1] + view.getHeight(); + } + @Override public void onKeyboardLoaded(KeyboardType keyboardType) { // Do nothing @@ -685,11 +719,99 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene // Refresh banner theme BannerController.setHTMLBanner(this, KeyboardType.KEYBOARD_TYPE_INAPP); resizeTextView(true); + updateKeyboardToolbarPosition(); + if (keyboardToolbarToggleButton != null) { + keyboardToolbarToggleButton.setVisibility(View.VISIBLE); + } } @Override public void onKeyboardDismissed() { resizeTextView(false); + setKeyboardToolbarExpanded(false, false); + if (keyboardToolbarToggleButton != null) { + keyboardToolbarToggleButton.setVisibility(View.GONE); + } + } + + private void initializeKeyboardToolbar() { + keyboardToolbarToggleButton = findViewById(R.id.keyboardToolbarToggleButton); + keyboardToolbarContainer = findViewById(R.id.keyboardToolbarContainer); + + if (keyboardToolbarToggleButton == null || keyboardToolbarContainer == null) { + return; + } + + setKeyboardToolbarExpanded(false, false); + keyboardToolbarToggleButton.setOnClickListener(v -> setKeyboardToolbarExpanded(!isKeyboardToolbarExpanded, true)); + + View shareButton = findViewById(R.id.keyboardToolbarShareButton); + if (shareButton != null) { + shareButton.setOnClickListener(v -> { + showShareDialog(); + setKeyboardToolbarExpanded(false, true); + }); + } + + View textSizeButton = findViewById(R.id.keyboardToolbarTextSizeButton); + if (textSizeButton != null) { + textSizeButton.setOnClickListener(v -> { + showTextSizeDialog(); + setKeyboardToolbarExpanded(false, true); + }); + } + + View clearButton = findViewById(R.id.keyboardToolbarClearButton); + if (clearButton != null) { + clearButton.setOnClickListener(v -> { + showClearTextDialog(); + setKeyboardToolbarExpanded(false, true); + }); + } + } + + private void setKeyboardToolbarExpanded(boolean expanded, boolean animateRotation) { + if (keyboardToolbarContainer == null || keyboardToolbarToggleButton == null) { + return; + } + + isKeyboardToolbarExpanded = expanded; + keyboardToolbarContainer.setVisibility(expanded ? View.VISIBLE : View.GONE); + + float targetRotation = expanded ? 180f : 0f; + if (animateRotation) { + keyboardToolbarToggleButton.animate().rotation(targetRotation).setDuration(180).start(); + } else { + keyboardToolbarToggleButton.setRotation(targetRotation); + } + } + + private void updateKeyboardToolbarPosition() { + int keyboardTopOffset = KMManager.getKeyboardHeight(this) + KMManager.getBannerHeight(this) + dpToPx(12); + updateBottomMargin(keyboardToolbarToggleButton, keyboardTopOffset); + updateBottomMargin(keyboardToolbarContainer, dpToPx(8)); + } + + private void updateBottomMargin(View view, int bottomMargin) { + if (view == null) { + return; + } + + ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); + if (!(layoutParams instanceof ViewGroup.MarginLayoutParams)) { + return; + } + + ViewGroup.MarginLayoutParams marginLayoutParams = (ViewGroup.MarginLayoutParams) layoutParams; + if (marginLayoutParams.bottomMargin != bottomMargin) { + marginLayoutParams.bottomMargin = bottomMargin; + view.setLayoutParams(marginLayoutParams); + } + } + + private int dpToPx(int dp) { + float density = getResources().getDisplayMetrics().density; + return Math.round(dp * density); } public void downloadKMP(String packageId, String bcp47, KmpInstallMode installMode) { diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/bg_keyboard_toolbar_container.xml b/android/KMAPro/kMAPro/src/main/res/drawable/bg_keyboard_toolbar_container.xml new file mode 100644 index 0000000000..1dfbf0c345 --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/drawable/bg_keyboard_toolbar_container.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/bg_keyboard_toolbar_toggle.xml b/android/KMAPro/kMAPro/src/main/res/drawable/bg_keyboard_toolbar_toggle.xml new file mode 100644 index 0000000000..08f8c630b4 --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/drawable/bg_keyboard_toolbar_toggle.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_arrow_dropdown.xml b/android/KMAPro/kMAPro/src/main/res/drawable/ic_arrow_dropdown.xml new file mode 100644 index 0000000000..38b4ddbe58 --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/drawable/ic_arrow_dropdown.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_keyboard_on_screen.xml b/android/KMAPro/kMAPro/src/main/res/drawable/ic_keyboard_on_screen.xml new file mode 100644 index 0000000000..608500ee7a --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/drawable/ic_keyboard_on_screen.xml @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_keyboard_physical.xml b/android/KMAPro/kMAPro/src/main/res/drawable/ic_keyboard_physical.xml new file mode 100644 index 0000000000..422e2bd5ed --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/drawable/ic_keyboard_physical.xml @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_warning.xml b/android/KMAPro/kMAPro/src/main/res/drawable/ic_warning.xml new file mode 100644 index 0000000000..7b9ff21539 --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/drawable/ic_warning.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/res/layout/activity_main.xml b/android/KMAPro/kMAPro/src/main/res/layout/activity_main.xml index 561b7128d4..b6459fb0e8 100644 --- a/android/KMAPro/kMAPro/src/main/res/layout/activity_main.xml +++ b/android/KMAPro/kMAPro/src/main/res/layout/activity_main.xml @@ -46,6 +46,68 @@ + + + + + + + + + + + diff --git a/android/KMAPro/kMAPro/src/main/res/layout/nav_checkbox_item.xml b/android/KMAPro/kMAPro/src/main/res/layout/nav_checkbox_item.xml index 6988d63664..5ec549a98b 100644 --- a/android/KMAPro/kMAPro/src/main/res/layout/nav_checkbox_item.xml +++ b/android/KMAPro/kMAPro/src/main/res/layout/nav_checkbox_item.xml @@ -1,10 +1,22 @@ - + android:clickable="false"> + + + + diff --git a/android/KMAPro/kMAPro/src/main/res/layout/nav_right_icon_item.xml b/android/KMAPro/kMAPro/src/main/res/layout/nav_right_icon_item.xml new file mode 100644 index 0000000000..312b033bfd --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/layout/nav_right_icon_item.xml @@ -0,0 +1,22 @@ + + + + + + \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/res/menu/main.xml b/android/KMAPro/kMAPro/src/main/res/menu/main.xml index 835228defe..bc49006fd3 100644 --- a/android/KMAPro/kMAPro/src/main/res/menu/main.xml +++ b/android/KMAPro/kMAPro/src/main/res/menu/main.xml @@ -21,57 +21,57 @@ - + + + + + - - + + + + + - + + + + + - + + + + + - + + + + + - + + + + + - + + + + + - + + + + + + - - + + + android:title="Current keyboard" + app:actionLayout="@layout/nav_right_icon_item" + /> + android:title="Installed Languages" + app:actionLayout="@layout/nav_right_icon_item"/> + android:title="Install Keyboard or Dictionary" + app:actionLayout="@layout/nav_right_icon_item"/> + android:title="Display Language" + app:actionLayout="@layout/nav_right_icon_item"/> + android:title="Adjust Keyboard Height" + app:actionLayout="@layout/nav_right_icon_item"/> + android:title="Adjust Longpress Delay" + app:actionLayout="@layout/nav_right_icon_item"/> - + + + + @@ -99,7 +106,13 @@ + android:title="User guide" + app:actionLayout="@layout/nav_right_icon_item"/> + From 52290aed5497b3188c711850576e05e50ada825f Mon Sep 17 00:00:00 2001 From: Mengchou97 Date: Mon, 30 Mar 2026 16:10:09 +0700 Subject: [PATCH 024/140] fixed the button close function --- .../com/tavultesoft/kmapro/MainActivity.java | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index 4cc1451568..e857073f96 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -146,6 +146,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene private View keyboardToolbarToggleButton; private View keyboardToolbarContainer; private boolean isKeyboardToolbarExpanded = false; + private boolean suppressOutsideCloseForCurrentTouch = false; private final int minTextSize = 16; private final int maxTextSize = 72; private int textSize = minTextSize; @@ -675,12 +676,19 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene @Override public boolean dispatchTouchEvent(MotionEvent ev) { - if (ev.getAction() == MotionEvent.ACTION_DOWN && isKeyboardToolbarExpanded) { - boolean touchOnToolbar = isTouchInsideView(ev, keyboardToolbarContainer); + int action = ev.getActionMasked(); + if (action == MotionEvent.ACTION_DOWN && isKeyboardToolbarExpanded && !suppressOutsideCloseForCurrentTouch) { boolean touchOnToggle = isTouchInsideView(ev, keyboardToolbarToggleButton); + if (touchOnToggle) { + return super.dispatchTouchEvent(ev); + } + + boolean touchOnToolbar = isTouchInsideView(ev, keyboardToolbarContainer); if (!touchOnToolbar && !touchOnToggle) { setKeyboardToolbarExpanded(false, true); } + } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { + suppressOutsideCloseForCurrentTouch = false; } return super.dispatchTouchEvent(ev); } @@ -690,15 +698,9 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene return false; } - int[] location = new int[2]; - view.getLocationOnScreen(location); - float x = ev.getRawX(); - float y = ev.getRawY(); - - return x >= location[0] - && x <= location[0] + view.getWidth() - && y >= location[1] - && y <= location[1] + view.getHeight(); + Rect bounds = new Rect(); + view.getGlobalVisibleRect(bounds); + return bounds.contains((int) ev.getRawX(), (int) ev.getRawY()); } @Override @@ -743,6 +745,15 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } setKeyboardToolbarExpanded(false, false); + keyboardToolbarToggleButton.setOnTouchListener((v, event) -> { + int action = event.getActionMasked(); + if (action == MotionEvent.ACTION_DOWN) { + suppressOutsideCloseForCurrentTouch = true; + } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { + suppressOutsideCloseForCurrentTouch = false; + } + return false; + }); keyboardToolbarToggleButton.setOnClickListener(v -> setKeyboardToolbarExpanded(!isKeyboardToolbarExpanded, true)); View shareButton = findViewById(R.id.keyboardToolbarShareButton); From 244cec475d04ebb063b1354527cb41d275310a04 Mon Sep 17 00:00:00 2001 From: Mengchou97 Date: Mon, 30 Mar 2026 16:41:55 +0700 Subject: [PATCH 025/140] adjusted the width of the sidebar to always wrap-content at 80% of the screen --- .../main/java/com/tavultesoft/kmapro/MainActivity.java | 8 ++++++++ .../KMAPro/kMAPro/src/main/res/layout/activity_main.xml | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index e857073f96..ad0088cf89 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -6,6 +6,8 @@ package com.tavultesoft.kmapro; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import com.google.android.material.navigation.NavigationView; +import android.util.DisplayMetrics; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; @@ -202,6 +204,12 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene setContentView(R.layout.activity_main); + NavigationView navView = findViewById(R.id.nav_view); + DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); + int width = (int) (displayMetrics.widthPixels * 0.80); + navView.getLayoutParams().width = width; + navView.requestLayout(); + constraintLayout = (ConstraintLayout)findViewById(R.id.constraintLayout); setupEdgeToEdge(R.id.constraintLayout); setupStatusBarColors(android.R.color.white, R.color.neutral_2); diff --git a/android/KMAPro/kMAPro/src/main/res/layout/activity_main.xml b/android/KMAPro/kMAPro/src/main/res/layout/activity_main.xml index b6459fb0e8..03da973fe1 100644 --- a/android/KMAPro/kMAPro/src/main/res/layout/activity_main.xml +++ b/android/KMAPro/kMAPro/src/main/res/layout/activity_main.xml @@ -113,7 +113,7 @@ Date: Thu, 2 Apr 2026 03:04:34 +0700 Subject: [PATCH 026/140] Splash Screen's Appearance Mode --- .../kMAPro/src/main/AndroidManifest.xml | 40 ++++++++------ .../com/tavultesoft/kmapro/MainActivity.java | 52 ++++++++++++++++++- .../java/com/tavultesoft/kmapro/MyApp.java | 15 ++++++ .../kmapro/SplashScreenActivity.java | 20 +++++-- .../src/main/res/drawable/ic_palette.xml | 10 ++++ .../res/layout/activity_splash_screen.xml | 13 +++-- .../kMAPro/src/main/res/layout/nav_header.xml | 2 +- .../KMAPro/kMAPro/src/main/res/menu/main.xml | 2 +- .../kMAPro/src/main/res/menu/nav_menu.xml | 4 ++ .../src/main/res/values-night/styles.xml | 37 ++++++++----- .../kMAPro/src/main/res/values/styles.xml | 37 +++++++++---- 11 files changed, 179 insertions(+), 53 deletions(-) create mode 100644 android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MyApp.java create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable/ic_palette.xml diff --git a/android/KMAPro/kMAPro/src/main/AndroidManifest.xml b/android/KMAPro/kMAPro/src/main/AndroidManifest.xml index 446405033e..d27db14302 100644 --- a/android/KMAPro/kMAPro/src/main/AndroidManifest.xml +++ b/android/KMAPro/kMAPro/src/main/AndroidManifest.xml @@ -17,21 +17,22 @@ https://support.google.com/googleplay/android-developer/answer/9214102# --> + android:supportsRtl="true" + > - @@ -42,19 +43,19 @@ - - - - + + + + + + + + - - + + + + + android:theme="@style/Theme.App.Starting"> + + + + + + + diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index dc7dd49040..0d0931b0b6 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -66,6 +66,7 @@ import androidx.activity.result.ActivityResultCallback; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.appcompat.app.ActionBarDrawerToggle; +import androidx.appcompat.app.AppCompatDelegate; import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.SwitchCompat; import androidx.appcompat.app.AlertDialog; @@ -88,6 +89,8 @@ import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import androidx.annotation.NonNull; import androidx.core.app.ActivityCompat; +import androidx.core.view.WindowCompat; +import androidx.core.view.WindowInsetsControllerCompat; import androidx.drawerlayout.widget.DrawerLayout; import androidx.drawerlayout.widget.DrawerLayout; @@ -153,11 +156,19 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene DownloadResultReceiver resultReceiver; private static ProgressDialog progressDialog; + private static final String PREFS_NAME = "settings"; + private static final String KEY_THEME_MODE = "theme_mode"; @Override protected void onCreate(Bundle savedInstanceState) { - setTheme(R.style.AppTheme); + // setTheme(R.style.AppTheme); + + SharedPreferences theme_prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE); + int savedMode = theme_prefs.getInt(KEY_THEME_MODE, AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM); + AppCompatDelegate.setDefaultNightMode(savedMode); + getDelegate().applyDayNight(); super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); context = this; checkSendCrashReport(); @@ -245,6 +256,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene if (imManager != null) { imManager.showInputMethodPicker(); } + } else if (id == R.id.nav_installed_languages) { drawerLayout.closeDrawers(); Intent intent = new Intent(context, LanguagesSettingsActivity.class); @@ -268,6 +280,9 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } else if (id == R.id.nav_about) { drawerLayout.closeDrawers(); startActivity(new Intent(context, InfoActivity.class)); + }else if (id == R.id.nav_palette){ + drawerLayout.closeDrawers(); + openThemeDialog(); } return true; } @@ -313,6 +328,40 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene checkGetStarted(); } + private void openThemeDialog() { + String[] options = {"Dark Mode", "Light Mode", "System Default"}; + + SharedPreferences themePrefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE); + int currentMode = themePrefs.getInt(KEY_THEME_MODE, AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM); + + int checkedItem; + if (currentMode == AppCompatDelegate.MODE_NIGHT_YES) checkedItem = 0; + else if (currentMode == AppCompatDelegate.MODE_NIGHT_NO) checkedItem = 1; + else checkedItem = 2; + + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("Choose Theme"); + + builder.setSingleChoiceItems(options, checkedItem, (dialog, which) -> { + int modeToApply; + switch (which) { + case 0: modeToApply = AppCompatDelegate.MODE_NIGHT_YES; break; + case 1: modeToApply = AppCompatDelegate.MODE_NIGHT_NO; break; + default: modeToApply = AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM; break; + } + // Save the choice + themePrefs.edit().putInt(KEY_THEME_MODE, modeToApply).apply(); + + // Apply immediately + AppCompatDelegate.setDefaultNightMode(modeToApply); + getDelegate().applyDayNight(); // ensures current activity updates + + dialog.dismiss(); + }); + builder.setNegativeButton("Cancel", (dialog, which) -> dialog.dismiss()); + builder.show(); + } + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) @Override public void onActivityResult(int requestCode, int resultCode, Intent returnIntent) { @@ -561,6 +610,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene return true; } + @Override public boolean onOptionsItemSelected(MenuItem item) { // Android Gradle 8.0 no longer declares resources final, so can't use switch statement here diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MyApp.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MyApp.java new file mode 100644 index 0000000000..0ac1ec574b --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MyApp.java @@ -0,0 +1,15 @@ +package com.tavultesoft.kmapro; + +import android.app.Application; +import android.content.SharedPreferences; +import androidx.appcompat.app.AppCompatDelegate; + +public class MyApp extends Application { + @Override + public void onCreate() { + super.onCreate(); + SharedPreferences prefs = getSharedPreferences("settings", MODE_PRIVATE); + int mode = prefs.getInt("theme_mode", AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM); + AppCompatDelegate.setDefaultNightMode(mode); + } +} \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/SplashScreenActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/SplashScreenActivity.java index 60d26d7508..fd913ece4e 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/SplashScreenActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/SplashScreenActivity.java @@ -1,25 +1,37 @@ package com.tavultesoft.kmapro; +import android.content.SharedPreferences; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.content.res.Configuration; import android.os.Bundle; import android.os.Handler; import android.app.Activity; import android.content.Intent; import android.widget.TextView; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.app.AppCompatDelegate; + import com.keyman.engine.util.KMLog; import java.util.Calendar; -public class SplashScreenActivity extends Activity { +public class SplashScreenActivity extends AppCompatActivity { private final static String TAG = "Splash"; - // For now, disable this Activity so app is not delayed showing version/copyright text - private static int SPLASH_TIME_OUT = 0; // msec + private static int SPLASH_TIME_OUT = 100; // msec @Override protected void onCreate(Bundle savedInstanceState) { + SharedPreferences prefs = getSharedPreferences("settings", MODE_PRIVATE); + int savedMode = prefs.getInt("theme_mode", AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM); + + if (savedMode == AppCompatDelegate.MODE_NIGHT_YES) { + setTheme(R.style.AppTheme_BrandedLaunchDark); + } else { + setTheme(R.style.AppTheme_BrandedLaunchLight); + } super.onCreate(savedInstanceState); setContentView(R.layout.activity_splash_screen); @@ -41,6 +53,8 @@ public class SplashScreenActivity extends Activity { String date = String.format("%s%s", copyright.getText(), year); //copyright.setText(date); + AppCompatDelegate.setDefaultNightMode(savedMode); + new Handler().postDelayed(new Runnable() { @Override public void run() { diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_palette.xml b/android/KMAPro/kMAPro/src/main/res/drawable/ic_palette.xml new file mode 100644 index 0000000000..f701027ccf --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/drawable/ic_palette.xml @@ -0,0 +1,10 @@ + + + diff --git a/android/KMAPro/kMAPro/src/main/res/layout/activity_splash_screen.xml b/android/KMAPro/kMAPro/src/main/res/layout/activity_splash_screen.xml index c6cde0e259..128cadc6e1 100644 --- a/android/KMAPro/kMAPro/src/main/res/layout/activity_splash_screen.xml +++ b/android/KMAPro/kMAPro/src/main/res/layout/activity_splash_screen.xml @@ -1,19 +1,18 @@ - - + \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/res/layout/nav_header.xml b/android/KMAPro/kMAPro/src/main/res/layout/nav_header.xml index a59be8fd9d..093647f9ad 100644 --- a/android/KMAPro/kMAPro/src/main/res/layout/nav_header.xml +++ b/android/KMAPro/kMAPro/src/main/res/layout/nav_header.xml @@ -12,7 +12,7 @@ diff --git a/android/KMAPro/kMAPro/src/main/res/menu/main.xml b/android/KMAPro/kMAPro/src/main/res/menu/main.xml index d98360a9a9..bf8da41f38 100644 --- a/android/KMAPro/kMAPro/src/main/res/menu/main.xml +++ b/android/KMAPro/kMAPro/src/main/res/menu/main.xml @@ -38,7 +38,7 @@ + android:icon="@drawable/ic_action_share_mode" /> + @android:color/black @android:color/black - @android:color/black + @android:color/white @android:color/white @style/PreferenceThemeOverlay - + + #131313 false true + @android:color/black + false + true + @style/AppTheme.PopupMenu - - - - - - - - + + + + + + + + + + diff --git a/android/KMAPro/kMAPro/src/main/res/values/styles.xml b/android/KMAPro/kMAPro/src/main/res/values/styles.xml index da85f8d32a..34f775595f 100644 --- a/android/KMAPro/kMAPro/src/main/res/values/styles.xml +++ b/android/KMAPro/kMAPro/src/main/res/values/styles.xml @@ -12,8 +12,6 @@ @color/complementary_5 - @android:color/white - @android:color/black @color/neutral_3 @android:color/black @@ -22,29 +20,48 @@ @style/AppTheme.PopupMenu @style/PreferenceThemeOverlay - + + @android:color/white false true + @android:color/white + true + true + --> + + + + + + + + - - - - - - + --> - - - - - + + + + + From c67f319ab5bf83490e3494dbbf59961a615d48ef Mon Sep 17 00:00:00 2001 From: kongchanlina Date: Mon, 6 Apr 2026 14:38:40 +0700 Subject: [PATCH 029/140] fixing status bar color --- .../kmapro/AdjustKeyboardHeightActivity.java | 19 ++++++++++- .../kmapro/AdjustLongpressDelayActivity.java | 18 ++++++++++- .../kmapro/KMPBrowserActivity.java | 18 ++++++++++- .../com/tavultesoft/kmapro/MainActivity.java | 32 +++++++++++++++++-- .../java/com/tavultesoft/kmapro/MyApp.java | 2 +- .../src/main/res/layout/activity_main.xml | 2 +- .../res/layout/activity_select_language.xml | 1 - .../kMAPro/src/main/res/values/colors.xml | 1 + 8 files changed, 85 insertions(+), 8 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustKeyboardHeightActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustKeyboardHeightActivity.java index 4bc419ced6..663b6b17ae 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustKeyboardHeightActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustKeyboardHeightActivity.java @@ -6,10 +6,12 @@ package com.tavultesoft.kmapro; import android.content.Context; import android.content.res.Configuration; import android.graphics.drawable.ColorDrawable; +import android.os.Build; import android.os.Bundle; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import android.view.Window; import android.widget.Button; import android.widget.ImageView; import android.widget.TextView; @@ -17,6 +19,7 @@ import android.widget.TextView; import androidx.appcompat.app.ActionBar; import androidx.appcompat.widget.Toolbar; import androidx.core.content.ContextCompat; +import androidx.core.view.WindowCompat; import com.keyman.engine.BaseActivity; import com.keyman.engine.KMManager; @@ -41,7 +44,21 @@ public class AdjustKeyboardHeightActivity extends BaseActivity { setContentView(R.layout.activity_adjust_keyboard_height); setupEdgeToEdge(R.id.adjust_keyboard_layout); - setupStatusBarColors(R.color.keyman_blue, android.R.color.white); +// setupStatusBarColors(R.color.keyman_blue, android.R.color.white); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + Window window = getWindow(); + window.setStatusBarColor(ContextCompat.getColor(this, R.color.keyman_blue)); + boolean isDarkBackground = true; + int flags = window.getDecorView().getSystemUiVisibility(); + if (isDarkBackground) { + flags &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; + } else { + flags |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; + } + window.getDecorView().setSystemUiVisibility(flags); + WindowCompat.setDecorFitsSystemWindows(window, false); + } Toolbar toolbar = findViewById(R.id.titlebar); setSupportActionBar(toolbar); diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java index e0d7c9d5f5..681277370f 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java @@ -8,14 +8,17 @@ package com.tavultesoft.kmapro; import android.content.Context; import android.content.SharedPreferences; import android.graphics.drawable.ColorDrawable; +import android.os.Build; import android.os.Bundle; import android.view.View; +import android.view.Window; import android.widget.SeekBar; import android.widget.TextView; import androidx.appcompat.app.ActionBar; import androidx.appcompat.widget.Toolbar; import androidx.core.content.ContextCompat; +import androidx.core.view.WindowCompat; import com.keyman.engine.BaseActivity; import com.keyman.engine.KMManager; @@ -59,7 +62,20 @@ public class AdjustLongpressDelayActivity extends BaseActivity { setContentView(R.layout.activity_adjust_longpress_delay); setupEdgeToEdge(R.id.adjust_longpress_layout); - setupStatusBarColors(R.color.keyman_blue, android.R.color.white); +// setupStatusBarColors(R.color.keyman_blue, android.R.color.white); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + Window window = getWindow(); + window.setStatusBarColor(ContextCompat.getColor(this, R.color.keyman_blue)); + boolean isDarkBackground = true; + int flags = window.getDecorView().getSystemUiVisibility(); + if (isDarkBackground) { + flags &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; + } else { + flags |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; + } + window.getDecorView().setSystemUiVisibility(flags); + WindowCompat.setDecorFitsSystemWindows(window, false); + } Toolbar toolbar = (Toolbar) findViewById(R.id.titlebar); setSupportActionBar(toolbar); diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KMPBrowserActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KMPBrowserActivity.java index 2a2b85a32d..8c02d777c3 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KMPBrowserActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KMPBrowserActivity.java @@ -16,12 +16,15 @@ import android.util.AndroidRuntimeException; import android.util.Log; import android.view.View; import android.view.ViewGroup; +import android.view.Window; import android.webkit.WebChromeClient; import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.Toast; import androidx.appcompat.app.AppCompatActivity; +import androidx.core.content.ContextCompat; +import androidx.core.view.WindowCompat; import com.keyman.engine.BaseActivity; import com.keyman.engine.KMManager; @@ -81,7 +84,20 @@ public class KMPBrowserActivity extends BaseActivity { } setupEdgeToEdge(R.id.kmp_browser_layout); - setupStatusBarColors(R.color.complementary_5, android.R.color.white); +// setupStatusBarColors(R.color.complementary_5, android.R.color.white); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + Window window = getWindow(); + window.setStatusBarColor(ContextCompat.getColor(this, R.color.keyman_blue)); + boolean isDarkBackground = true; + int flags = window.getDecorView().getSystemUiVisibility(); + if (isDarkBackground) { + flags &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; + } else { + flags |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; + } + window.getDecorView().setSystemUiVisibility(flags); + WindowCompat.setDecorFitsSystemWindows(window, false); + } webView = (WebView) findViewById(R.id.kmpBrowserWebView); webView.getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL); diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index e2b88287df..6c7387d61c 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -117,6 +117,7 @@ import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; +import android.view.Window; import android.view.WindowManager; import android.widget.Button; import android.widget.ImageView; @@ -227,8 +228,35 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene navView.requestLayout(); constraintLayout = (ConstraintLayout)findViewById(R.id.constraintLayout); - setupEdgeToEdge(R.id.constraintLayout); - setupStatusBarColors(android.R.color.white, R.color.neutral_2); +// setupEdgeToEdge(R.id.constraintLayout); +// setupStatusBarColors(android.R.color.white, R.color.neutral_2); + // START OF FIXED STATUS BAR LOGIC + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + Window window = getWindow(); + window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + + // Set the status bar color based on your dynamic color resource + window.setStatusBarColor(ContextCompat.getColor(this, R.color.toolbarColor)); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + int flags = window.getDecorView().getSystemUiVisibility(); + + // Check if the system is currently in Night Mode + boolean isDarkMode = (getResources().getConfiguration().uiMode & + Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES; + + if (!isDarkMode) { + // LIGHT MODE: Make icons BLACK so they show up on white + flags |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; + } else { + // DARK MODE: Make icons WHITE (by removing the light flag) + flags &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; + } + window.getDecorView().setSystemUiVisibility(flags); + } + } + toolbar = (Toolbar) findViewById(R.id.titlebar); setSupportActionBar(toolbar); diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MyApp.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MyApp.java index 0ac1ec574b..ddb494450a 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MyApp.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MyApp.java @@ -7,9 +7,9 @@ import androidx.appcompat.app.AppCompatDelegate; public class MyApp extends Application { @Override public void onCreate() { - super.onCreate(); SharedPreferences prefs = getSharedPreferences("settings", MODE_PRIVATE); int mode = prefs.getInt("theme_mode", AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM); AppCompatDelegate.setDefaultNightMode(mode); + super.onCreate(); } } \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/res/layout/activity_main.xml b/android/KMAPro/kMAPro/src/main/res/layout/activity_main.xml index 03da973fe1..c7a3e46821 100644 --- a/android/KMAPro/kMAPro/src/main/res/layout/activity_main.xml +++ b/android/KMAPro/kMAPro/src/main/res/layout/activity_main.xml @@ -14,7 +14,7 @@ android:layout_height="match_parent" android:layout_above="@+id/KMKeyboard" android:orientation="vertical" - android:background="@android:color/white" + android:background="@color/toolbarColor" tools:context=".MainActivity" android:fitsSystemWindows="true"> diff --git a/android/KMAPro/kMAPro/src/main/res/layout/activity_select_language.xml b/android/KMAPro/kMAPro/src/main/res/layout/activity_select_language.xml index a14190cf59..eea3415394 100644 --- a/android/KMAPro/kMAPro/src/main/res/layout/activity_select_language.xml +++ b/android/KMAPro/kMAPro/src/main/res/layout/activity_select_language.xml @@ -38,6 +38,5 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintVertical_bias="1.0"/> - diff --git a/android/KMAPro/kMAPro/src/main/res/values/colors.xml b/android/KMAPro/kMAPro/src/main/res/values/colors.xml index b8197e561a..4f59937b80 100644 --- a/android/KMAPro/kMAPro/src/main/res/values/colors.xml +++ b/android/KMAPro/kMAPro/src/main/res/values/colors.xml @@ -35,5 +35,6 @@ #FFFFFF @color/keyman_blue + From 409b9b04208ce03f5b62edf986a0ba17d696ae4a Mon Sep 17 00:00:00 2001 From: kongchanlina Date: Mon, 6 Apr 2026 15:41:09 +0700 Subject: [PATCH 030/140] fixing status bar --- .../com/keyman/android/SystemKeyboard.java | 359 ++++++++++++++++++ 1 file changed, 359 insertions(+) create mode 100644 android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java diff --git a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java new file mode 100644 index 0000000000..570f4b6c47 --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java @@ -0,0 +1,359 @@ +package com.keyman.android; + +import com.keyman.engine.util.DownloadFileUtils; +import com.tavultesoft.kmapro.BuildConfig; +import com.tavultesoft.kmapro.DefaultLanguageResource; +import com.tavultesoft.kmapro.KeymanSettingsActivity; +import com.tavultesoft.kmapro.PreferencesManager; +import com.keyman.engine.KMManager; +import com.keyman.engine.KMManager.KeyboardType; +import com.keyman.engine.KMHardwareKeyboardInterpreter; +import com.keyman.engine.KMManager.SuggestionType; +import com.keyman.engine.KeyboardEventHandler.OnKeyboardEventListener; +import com.keyman.engine.R; +import com.keyman.engine.data.Keyboard; +import com.keyman.engine.util.DependencyUtil; +import com.keyman.engine.util.DependencyUtil.LibraryType; + +import android.content.Context; +import android.content.SharedPreferences; +import android.content.res.Configuration; +import android.graphics.Point; +import android.inputmethodservice.InputMethodService; +import android.text.InputType; +import android.util.Log; +import android.view.KeyEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.ExtractedText; +import android.view.inputmethod.ExtractedTextRequest; +import android.view.inputmethod.InputConnection; +import android.widget.FrameLayout; +import android.content.Intent; + +import io.sentry.android.core.SentryAndroid; +import io.sentry.Sentry; + + + /** + * Copyright (C) 2017 SIL International. All rights reserved. + */ + + public class SystemKeyboard extends InputMethodService implements OnKeyboardEventListener { + + private static View inputView = null; + private KMHardwareKeyboardInterpreter interpreter = null; + private int inputType = InputType.TYPE_NULL; + private int lastOrientation = Configuration.ORIENTATION_UNDEFINED; + + private static final String TAG = "SystemKeyboard"; + + /** + * Main initialization of the input method component. Be sure to call + * to super class. + */ + @Override + public void onCreate() { + super.onCreate(); + + if (DependencyUtil.libraryExists(LibraryType.SENTRY) && !Sentry.isEnabled()) { + Log.d(TAG, "Initializing Sentry"); + SentryAndroid.init(getApplicationContext(), options -> { + options.setEnableAutoSessionTracking(false); + options.setRelease(com.tavultesoft.kmapro.BuildConfig.KEYMAN_VERSION_GIT_TAG); + options.setEnvironment(com.tavultesoft.kmapro.BuildConfig.KEYMAN_VERSION_ENVIRONMENT); + }); + } + if (BuildConfig.DEBUG) { + KMManager.setDebugMode(true); + } + KMManager.addKeyboardEventListener(this); + Context context = getApplicationContext(); + KMManager.initialize(context, KeyboardType.KEYBOARD_TYPE_SYSTEM); + DefaultLanguageResource.install(context); + interpreter = new KMHardwareKeyboardInterpreter(context, KeyboardType.KEYBOARD_TYPE_SYSTEM); + KMManager.setInputMethodService(this); // for HW interface + + SharedPreferences prefs = context.getSharedPreferences(context.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); + KMManager.SpacebarText spacebarText = KMManager.SpacebarText.fromString(prefs.getString(KeymanSettingsActivity.spacebarTextKey, KMManager.SpacebarText.LANGUAGE_KEYBOARD.toString())); + KMManager.setSpacebarText(spacebarText); + + // Set the system keyboard HTML banner + BannerController.setHTMLBanner(this, KeyboardType.KEYBOARD_TYPE_SYSTEM); + + boolean mayHaveHapticFeedback = prefs.getBoolean(KeymanSettingsActivity.hapticFeedbackKey, false); + KMManager.setHapticFeedback(mayHaveHapticFeedback); + + // Checking for updates should never be allowed to crash the keyboard. + // Just silently fail if this occurs. + if(DownloadFileUtils.getDownloadManager(this) != null) { + // Will try to emit a toast if it fails - i.e., is not silent. + KMManager.executeResourceUpdate(this); + } + } + + + @Override + public void onDestroy() { + inputView = null; + KMManager.removeKeyboardEventListener(this); + interpreter = null; // Throw it away, since we're losing our application's context. + KMManager.onDestroy(); + super.onDestroy(); + } + + /** + * This is the point where you can do all of your UI initialization. It + * is called after creation and any configuration change. + */ + @Override + public void onInitializeInterface() { + super.onInitializeInterface(); + } + + /** + * Called by the framework when your view for creating input needs to + * be generated. This will be called the first time your input method + * is displayed, and every time it needs to be re-created such as due to + * a configuration change. + */ + @Override + public View onCreateInputView() { + if (inputView == null) { + inputView = KMManager.createInputView(this); + } + + ViewGroup parent = (ViewGroup) inputView.getParent(); + if (parent != null) { + parent.removeView(inputView); + } + + return inputView; + } + + /** + * Deal with the editor reporting movement of its cursor. + */ + @Override + public void onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd) { + super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, candidatesStart, candidatesEnd); + KMManager.updateSelectionRange(KMManager.KeyboardType.KEYBOARD_TYPE_SYSTEM); + } + + private void sendCurrentFontName() { + Keyboard keyboardInfo = KMManager.getCurrentKeyboardInfo(this); + if (keyboardInfo != null) { + String fontName = keyboardInfo.getFont(); + + Intent intent; + if (BuildConfig.DEBUG) { + intent = new Intent("com.tavultesoft.kmapro.debug.keyboard_changed"); + } else { + intent = new Intent("com.tavultesoft.kmapro.keyboard_changed"); + } + intent.putExtra("fontName", fontName); + sendBroadcast(intent); + } + } + + /** + * This is the main point where we do our initialization of the input method + * to begin operating on an application. At this point we have been + * bound to the client, and are now receiving all of the detailed information + * about the target of our edits. + */ + @Override + public void onStartInput(EditorInfo attribute, boolean restarting) { + attribute.imeOptions |= EditorInfo.IME_FLAG_NO_EXTRACT_UI | EditorInfo.IME_FLAG_NO_FULLSCREEN; + super.onStartInput(attribute, restarting); + KMManager.onStartInput(attribute, restarting); + KMManager.resetContext(KeyboardType.KEYBOARD_TYPE_SYSTEM); + + Context appContext = getApplicationContext(); + // Temporarily disable predictions on certain fields (e.g. hidden password field or numeric) + inputType = attribute.inputType; + KMManager.setPredictionsSuspended(inputType, KeyboardType.KEYBOARD_TYPE_SYSTEM); + if (KMManager.getPredictionsSuspended(KeyboardType.KEYBOARD_TYPE_SYSTEM)) { + KMManager.setBannerOptions(false); + // Set the system keyboard HTML banner + BannerController.setHTMLBanner(this, KeyboardType.KEYBOARD_TYPE_SYSTEM); + } else if (KMManager.isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_SYSTEM)){ + // Check if predictions needs to be re-enabled per Settings preference + Keyboard kbInfo = KMManager.getCurrentKeyboardInfo(appContext); + if (kbInfo != null) { + String langId = kbInfo.getLanguageID(); + SharedPreferences prefs = appContext.getSharedPreferences(appContext.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); + int maySuggest = prefs.getInt(KMManager.getLanguageAutoCorrectionPreferenceKey(langId), KMManager.KMDefault_Suggestion); + // Enable banner if maySuggest is not SuggestionType.SUGGESTIONS_DISABLED (0) + KMManager.setBannerOptions(maySuggest != SuggestionType.SUGGESTIONS_DISABLED.toInt()); + } else { + KMManager.setBannerOptions(false); + } + } + + // Determine special handling for ENTER key + KMManager.setEnterMode(attribute.imeOptions, inputType); + + + InputConnection ic = getCurrentInputConnection(); + if (ic != null) { + ExtractedText icText = ic.getExtractedText(new ExtractedTextRequest(), 0); + /* + We do sometimes receive null `icText.text`, even though + getExtractedText() docs does not list this as a possible + return value, so we test for that as well (#11479) + */ + if (icText != null && icText.text != null) { + // Update the text selection but ignore the returned statuses + KMManager.updateText(KeyboardType.KEYBOARD_TYPE_SYSTEM, icText.text.toString()); + KMManager.updateSelectionRange(KeyboardType.KEYBOARD_TYPE_SYSTEM); + } + } + + // Select numeric layer if applicable + if (KMManager.isNumericField(inputType)) { + KMManager.setNumericLayer(KeyboardType.KEYBOARD_TYPE_SYSTEM); + } + + if (KMManager.isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_SYSTEM)) { + sendCurrentFontName(); + } + } + + @Override + public void onStartInputView(EditorInfo attribute, boolean restarting) { + super.onStartInputView(attribute, restarting); + setInputView(onCreateInputView()); + + // Update the input type + inputType = attribute.inputType; + } + + @Override + public void onUpdateExtractingVisibility(EditorInfo ei) { + super.onUpdateExtractingVisibility(ei); + } + + @Override + public void onConfigureWindow(Window win, boolean isFullscreen, boolean isCandidatesOnly) { + super.onConfigureWindow(win, isFullscreen, isCandidatesOnly); + + // We don't currently use isFullscreen or isCandidatesOnly; we always want to MATCH_PARENT, + // unlike the default for height which is WRAP_CONTENT. We then adjust the touchable area + // in `onCalculateInsets` + win.setLayout(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT); + } + + @Override + public void onComputeInsets(InputMethodService.Insets outInsets) { + super.onComputeInsets(outInsets); + + // This method (likely) includes the IME equivalent to `onResume` for `Activity`-based classes, + // making it an important time to detect orientation changes. + Context appContext = getApplicationContext(); + int newOrientation = KMManager.getOrientation(appContext); + if(newOrientation != lastOrientation) { + lastOrientation = newOrientation; + Configuration newConfig = this.getResources().getConfiguration(); + KMManager.onConfigurationChanged(newConfig); + } + + // Update the touchable region of the Keyman keyboard + Point size = KMManager.getWindowSize(getApplicationContext()); + + int inputViewHeight = 0; + if (inputView != null) { + inputViewHeight = inputView.getHeight(); + } + + int navigationHeight = KMManager.getNavigationBarHeight(this, KeyboardType.KEYBOARD_TYPE_SYSTEM); + int bannerHeight = KMManager.getBannerHeight(this); + int kbHeight = KMManager.getKeyboardHeight(this); + outInsets.contentTopInsets = inputViewHeight - bannerHeight - kbHeight - navigationHeight; + outInsets.visibleTopInsets = outInsets.contentTopInsets; + outInsets.touchableInsets = InputMethodService.Insets.TOUCHABLE_INSETS_REGION; + outInsets.touchableRegion.set(0, outInsets.contentTopInsets, size.x, size.y); + } + + @Override + public void onKeyboardLoaded(KeyboardType keyboardType) { + // Do nothing + } + + @Override + public void onKeyboardChanged(String newKeyboard) { + // Refresh banner theme + BannerController.setHTMLBanner(this, KeyboardType.KEYBOARD_TYPE_SYSTEM); + KMManager.showSystemKeyboard(); + sendCurrentFontName(); + } + + @Override + public void onKeyboardShown() { + // Refresh banner theme + BannerController.setHTMLBanner(this, KeyboardType.KEYBOARD_TYPE_SYSTEM); + } + + @Override + public void onKeyboardDismissed() { + // Do nothing + } + + + @Override + public boolean onEvaluateInputViewShown() { + // On Android API 36+, the OSK defaults to not appearing when a physical keyboard is connected. + // If the default implementation returns true, recommend honoring it + // Reference: https://android.googlesource.com/platform/frameworks/base/+/7b739a8%5E%21/ + if (super.onEvaluateInputViewShown()) { + return true; + }; + + Context context = getApplicationContext(); + SharedPreferences prefs = context.getSharedPreferences(PreferencesManager.kma_prefs_name, Context.MODE_PRIVATE); + boolean showOSK = prefs.getBoolean(KeymanSettingsActivity.oskWithPhysicalKeyboardKey, false); + return showOSK; + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + // Determine if Physical keystroke should be passed off + if (inputType == InputType.TYPE_NULL) { + return false; // Revert to default handling + } + + if (event.getAction() == KeyEvent.ACTION_DOWN) { + switch (keyCode) { + case KeyEvent.KEYCODE_BACK: + // Dismiss the keyboard if currently shown + if (isInputViewShown()) { + KMManager.hideSystemKeyboard(); + return true; + } + break; + } + } + + return interpreter.onKeyDown(keyCode, event); // if false, will revert to default handling. + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + return interpreter.onKeyUp(keyCode, event); + } + + @Override + public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) { + return interpreter.onKeyMultiple(keyCode, count, event); + } + + @Override + public boolean onKeyLongPress(int keyCode, KeyEvent event) { + return interpreter.onKeyLongPress(keyCode, event); + } + } + + + From 53356253c8a126daaa2a07accbc1b8adb46a4c62 Mon Sep 17 00:00:00 2001 From: Mengchou97 Date: Mon, 6 Apr 2026 23:03:50 +0700 Subject: [PATCH 031/140] minor adjustment to the string and the name of the side panel --- android/KMAPro/kMAPro/src/main/res/menu/nav_menu.xml | 2 +- .../KMAPro/kMAPro/src/main/res/values/strings.xml | 12 +++--------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/res/menu/nav_menu.xml b/android/KMAPro/kMAPro/src/main/res/menu/nav_menu.xml index 81bc1e1149..e5f9a0b4d9 100644 --- a/android/KMAPro/kMAPro/src/main/res/menu/nav_menu.xml +++ b/android/KMAPro/kMAPro/src/main/res/menu/nav_menu.xml @@ -41,7 +41,7 @@ diff --git a/android/KMAPro/kMAPro/src/main/res/values/strings.xml b/android/KMAPro/kMAPro/src/main/res/values/strings.xml index f7bb70834e..910471a43c 100644 --- a/android/KMAPro/kMAPro/src/main/res/values/strings.xml +++ b/android/KMAPro/kMAPro/src/main/res/values/strings.xml @@ -14,18 +14,12 @@ Web Browser - - - - - - - + Show keyboard name on spacebar When a physical keyboard is connected - + When on, crash reports will be sent - + Learn how to use the keyboard Help and QR code for the active keyboard Current keyboard From 45590e531285c4d709e9a0a2447ccf1aab658a0c Mon Sep 17 00:00:00 2001 From: Mengchou97 Date: Tue, 7 Apr 2026 21:17:20 +0700 Subject: [PATCH 032/140] updated the set keyman as default option function --- .../kmapro/KeymanSettingsActivity.java | 13 ++++ .../com/tavultesoft/kmapro/MainActivity.java | 65 +++++++++---------- .../kMAPro/src/main/res/menu/nav_menu.xml | 5 ++ 3 files changed, 50 insertions(+), 33 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsActivity.java index fecbc6cddb..34908eab03 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsActivity.java @@ -1,6 +1,7 @@ package com.tavultesoft.kmapro; import android.os.Bundle; +import android.view.inputmethod.InputMethodManager; import com.keyman.engine.BaseActivity; import com.keyman.engine.KMManager; @@ -14,8 +15,10 @@ public class KeymanSettingsActivity extends BaseActivity { public static final String spacebarTextKey = "SpacebarText"; public static final String hapticFeedbackKey = "HapticFeedback"; public static final String oskWithPhysicalKeyboardKey = "ShowOSK"; + public static final String openDefaultKeyboardPickerKey = "OpenDefaultKeyboardPicker"; protected KeymanSettingsFragment innerFragment; + private boolean openDefaultKeyboardPickerPending = false; @Override protected void onCreate(Bundle savedInstanceState) { @@ -46,6 +49,8 @@ public class KeymanSettingsActivity extends BaseActivity { // For Keyman sites, disable keyboard picker task flag so keyboard picker doesn't dismiss Keyman app KMManager.dontCloseParentAppOnShowKeyboardPicker(); + + openDefaultKeyboardPickerPending = getIntent().getBooleanExtra(openDefaultKeyboardPickerKey, false); } @Override @@ -54,6 +59,14 @@ public class KeymanSettingsActivity extends BaseActivity { if(hasFocus) { innerFragment.update(); + + if (openDefaultKeyboardPickerPending) { + openDefaultKeyboardPickerPending = false; + InputMethodManager imManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); + if (imManager != null) { + getWindow().getDecorView().post(imManager::showInputMethodPicker); + } + } } } } diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index 6c7387d61c..69c6865820 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -305,13 +305,13 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene drawerLayout.closeDrawers(); startActivity(new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS)); } else if (id == R.id.nav_checkbox_set_default_keyboard) { - drawerLayout.closeDrawers(); - InputMethodManager imManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); - if (imManager != null) { - imManager.showInputMethodPicker(); - } + showSettingsAndOpenDefaultKeyboardPicker(); - } else if (id == R.id.nav_installed_languages) { + } + else if (item.getItemId() == R.id.action_settings) { + showSettings(); + return true;} + else if (id == R.id.nav_installed_languages) { drawerLayout.closeDrawers(); Intent intent = new Intent(context, LanguagesSettingsActivity.class); intent.putExtra(KMManager.KMKey_DisplayKeyboardSwitcher, false); @@ -1426,30 +1426,13 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } private void initializeDrawerItemSubtitles(NavigationView navigationView) { -// setDrawerItemSubtitle(navigationView, R.id.nav_installed_languages, -// getString(R.string.drawer_subtitle_installed_languages)); -// setDrawerItemSubtitle(navigationView, R.id.nav_install_keyboard, -// getString(R.string.drawer_subtitle_install_keyboard)); -// setDrawerItemSubtitle(navigationView, R.id.nav_checkbox_enable_system_keyboard, -// getString(R.string.drawer_subtitle_enable_system_keyboard)); -// setDrawerItemSubtitle(navigationView, R.id.nav_checkbox_set_default_keyboard, -// getString(R.string.drawer_subtitle_set_default_keyboard)); -// setDrawerItemSubtitle(navigationView, R.id.nav_display_language, -// getString(R.string.drawer_subtitle_display_language)); -// setDrawerItemSubtitle(navigationView, R.id.nav_keyboard_height, -// getString(R.string.drawer_subtitle_keyboard_height)); -// setDrawerItemSubtitle(navigationView, R.id.nav_longpress_delay, -// getString(R.string.drawer_subtitle_longpress_delay)); + setDrawerItemSubtitle(navigationView, R.id.nav_spacebar_caption, getString(R.string.drawer_subtitle_spacebar_caption)); setDrawerItemSubtitle(navigationView, R.id.nav_toggle_show_osk, getString(R.string.drawer_subtitle_show_osk)); -// setDrawerItemSubtitle(navigationView, R.id.nav_toggle_haptic_feedback, -// getString(R.string.drawer_subtitle_haptic_feedback)); setDrawerItemSubtitle(navigationView, R.id.nav_toggle_send_crash_report, getString(R.string.drawer_subtitle_send_crash_report)); -// setDrawerItemSubtitle(navigationView, R.id.nav_settings, -// getString(R.string.drawer_subtitle_more_settings)); setDrawerItemSubtitle(navigationView, R.id.nav_help, getString(R.string.drawer_subtitle_about)); setDrawerItemSubtitle(navigationView, R.id.nav_about_current_keyboard, @@ -1566,18 +1549,21 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene public void run() { startActivity(new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS)); } - }); + }, true); bindDrawerCheckboxAction(navigationView, R.id.nav_checkbox_set_default_keyboard, new Runnable() { @Override public void run() { - InputMethodManager imManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); - if (imManager != null) { - imManager.showInputMethodPicker(); - } + showSettingsAndOpenDefaultKeyboardPicker(); } - }); + }, true); + } + + private void showSettingsAndOpenDefaultKeyboardPicker() { + Intent settingsIntent = new Intent(this, KeymanSettingsActivity.class); + settingsIntent.putExtra(KeymanSettingsActivity.openDefaultKeyboardPickerKey, true); + startActivity(settingsIntent); } private void refreshDrawerSystemKeyboardCheckboxes(NavigationView navigationView) { @@ -1642,7 +1628,8 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } } - private void bindDrawerCheckboxAction(NavigationView navigationView, int menuItemId, final Runnable onActivate) { + private void bindDrawerCheckboxAction(NavigationView navigationView, int menuItemId, + final Runnable onActivate, final boolean closeDrawerAfterAction) { MenuItem menuItem = navigationView.getMenu().findItem(menuItemId); if (menuItem == null) { return; @@ -1666,8 +1653,20 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene return; } - checkBox.setOnClickListener(v -> onActivate.run()); - actionView.setOnClickListener(v -> onActivate.run()); + checkBox.setOnClickListener(v -> runDrawerCheckboxAction(onActivate, closeDrawerAfterAction)); + actionView.setOnClickListener(v -> runDrawerCheckboxAction(onActivate, closeDrawerAfterAction)); + } + + private void runDrawerCheckboxAction(final Runnable onActivate, boolean closeDrawerAfterAction) { + if (onActivate == null) { + return; + } + + onActivate.run(); + + if (closeDrawerAfterAction && drawerLayout != null) { + drawerLayout.closeDrawers(); + } } private void setDrawerCheckboxState(NavigationView navigationView, int menuItemId, boolean isChecked) { diff --git a/android/KMAPro/kMAPro/src/main/res/menu/nav_menu.xml b/android/KMAPro/kMAPro/src/main/res/menu/nav_menu.xml index e5f9a0b4d9..fd2ac65938 100644 --- a/android/KMAPro/kMAPro/src/main/res/menu/nav_menu.xml +++ b/android/KMAPro/kMAPro/src/main/res/menu/nav_menu.xml @@ -22,6 +22,11 @@ android:title="Current keyboard" app:actionLayout="@layout/nav_right_icon_item" /> + Date: Tue, 7 Apr 2026 22:26:34 +0700 Subject: [PATCH 033/140] finally finished the set keyman as default keyboard --- .../kmapro/GetStartedActivity.java | 38 +++++++++++-- .../kmapro/KeymanSettingsActivity.java | 13 ----- .../com/tavultesoft/kmapro/MainActivity.java | 55 ++++++++----------- 3 files changed, 57 insertions(+), 49 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/GetStartedActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/GetStartedActivity.java index c9d9aae584..f26c2c7220 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/GetStartedActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/GetStartedActivity.java @@ -36,14 +36,25 @@ public class GetStartedActivity extends BaseActivity { private static ArrayList> list = null; private static KMListAdapter listAdapter = null; protected static final String showGetStartedKey = "ShowGetStarted"; + public static final String openDefaultKeyboardPickerOnlyKey = "OpenDefaultKeyboardPickerOnly"; private final String iconKey = "icon"; private final String textKey = "text"; private final String isEnabledKey = "isEnabled"; + private boolean openDefaultKeyboardPickerOnly = false; + private boolean didAutoOpenDefaultKeyboardPicker = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); supportRequestWindowFeature(Window.FEATURE_NO_TITLE); + openDefaultKeyboardPickerOnly = getIntent().getBooleanExtra(openDefaultKeyboardPickerOnlyKey, false); + + if (openDefaultKeyboardPickerOnly) { + // Picker-only mode: intentionally skip rendering Get Started UI. + KMManager.dontCloseParentAppOnShowKeyboardPicker(); + return; + } + final Context context = this; setContentView(R.layout.get_started_list_layout); @@ -161,15 +172,24 @@ public class GetStartedActivity extends BaseActivity { super.onResume(); } - @Override - protected void onPause() { - super.onPause(); - } - @Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); if (hasFocus) { + if (openDefaultKeyboardPickerOnly) { + if (!didAutoOpenDefaultKeyboardPicker) { + didAutoOpenDefaultKeyboardPicker = true; + InputMethodManager imManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + if (imManager != null) { + imManager.showInputMethodPicker(); + } + } else { + // Focus returned after the picker was dismissed, so close the host activity. + finish(); + } + return; + } + // Enumerated steps are replaced with checkboxes when the user completes a step String one = String.valueOf(R.drawable.ic_looks_one); String two = String.valueOf(R.drawable.ic_looks_two); @@ -209,4 +229,12 @@ public class GetStartedActivity extends BaseActivity { uncheckGetStartedIfComplete(); } } + + @Override + protected void onPause() { + super.onPause(); + if (openDefaultKeyboardPickerOnly && didAutoOpenDefaultKeyboardPicker) { + finish(); + } + } } \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsActivity.java index 34908eab03..fecbc6cddb 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsActivity.java @@ -1,7 +1,6 @@ package com.tavultesoft.kmapro; import android.os.Bundle; -import android.view.inputmethod.InputMethodManager; import com.keyman.engine.BaseActivity; import com.keyman.engine.KMManager; @@ -15,10 +14,8 @@ public class KeymanSettingsActivity extends BaseActivity { public static final String spacebarTextKey = "SpacebarText"; public static final String hapticFeedbackKey = "HapticFeedback"; public static final String oskWithPhysicalKeyboardKey = "ShowOSK"; - public static final String openDefaultKeyboardPickerKey = "OpenDefaultKeyboardPicker"; protected KeymanSettingsFragment innerFragment; - private boolean openDefaultKeyboardPickerPending = false; @Override protected void onCreate(Bundle savedInstanceState) { @@ -49,8 +46,6 @@ public class KeymanSettingsActivity extends BaseActivity { // For Keyman sites, disable keyboard picker task flag so keyboard picker doesn't dismiss Keyman app KMManager.dontCloseParentAppOnShowKeyboardPicker(); - - openDefaultKeyboardPickerPending = getIntent().getBooleanExtra(openDefaultKeyboardPickerKey, false); } @Override @@ -59,14 +54,6 @@ public class KeymanSettingsActivity extends BaseActivity { if(hasFocus) { innerFragment.update(); - - if (openDefaultKeyboardPickerPending) { - openDefaultKeyboardPickerPending = false; - InputMethodManager imManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); - if (imManager != null) { - getWindow().getDecorView().post(imManager::showInputMethodPicker); - } - } } } } diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index 69c6865820..7ac93baef0 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -305,7 +305,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene drawerLayout.closeDrawers(); startActivity(new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS)); } else if (id == R.id.nav_checkbox_set_default_keyboard) { - showSettingsAndOpenDefaultKeyboardPicker(); + showDefaultKeyboardPickerFromMainPage(); } else if (item.getItemId() == R.id.action_settings) { @@ -1544,26 +1544,21 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene private void initializeDrawerCheckboxOptions(NavigationView navigationView) { bindDrawerCheckboxAction(navigationView, R.id.nav_checkbox_enable_system_keyboard, - new Runnable() { - @Override - public void run() { - startActivity(new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS)); + () -> { + if (drawerLayout != null) { + drawerLayout.closeDrawers(); } - }, true); + startActivity(new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS)); + }); bindDrawerCheckboxAction(navigationView, R.id.nav_checkbox_set_default_keyboard, - new Runnable() { - @Override - public void run() { - showSettingsAndOpenDefaultKeyboardPicker(); - } - }, true); + this::showDefaultKeyboardPickerFromMainPage); } - private void showSettingsAndOpenDefaultKeyboardPicker() { - Intent settingsIntent = new Intent(this, KeymanSettingsActivity.class); - settingsIntent.putExtra(KeymanSettingsActivity.openDefaultKeyboardPickerKey, true); - startActivity(settingsIntent); + private void showDefaultKeyboardPickerFromMainPage() { + Intent pickerIntent = new Intent(this, GetStartedActivity.class); + pickerIntent.putExtra(GetStartedActivity.openDefaultKeyboardPickerOnlyKey, true); + startActivity(pickerIntent); } private void refreshDrawerSystemKeyboardCheckboxes(NavigationView navigationView) { @@ -1629,7 +1624,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } private void bindDrawerCheckboxAction(NavigationView navigationView, int menuItemId, - final Runnable onActivate, final boolean closeDrawerAfterAction) { + final Runnable onActivate) { MenuItem menuItem = navigationView.getMenu().findItem(menuItemId); if (menuItem == null) { return; @@ -1653,20 +1648,18 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene return; } - checkBox.setOnClickListener(v -> runDrawerCheckboxAction(onActivate, closeDrawerAfterAction)); - actionView.setOnClickListener(v -> runDrawerCheckboxAction(onActivate, closeDrawerAfterAction)); - } - - private void runDrawerCheckboxAction(final Runnable onActivate, boolean closeDrawerAfterAction) { - if (onActivate == null) { - return; - } - - onActivate.run(); - - if (closeDrawerAfterAction && drawerLayout != null) { - drawerLayout.closeDrawers(); - } + checkBox.setClickable(true); + checkBox.setFocusable(false); + checkBox.setOnClickListener(v -> { + if (onActivate != null) { + onActivate.run(); + } + }); + actionView.setOnClickListener(v -> { + if (onActivate != null) { + onActivate.run(); + } + }); } private void setDrawerCheckboxState(NavigationView navigationView, int menuItemId, boolean isChecked) { From 97fad5535cd39f5302f91a70d33279636bf6aef6 Mon Sep 17 00:00:00 2001 From: Mengchou97 Date: Tue, 7 Apr 2026 22:41:45 +0700 Subject: [PATCH 034/140] updated the update keyboard function --- .../com/tavultesoft/kmapro/MainActivity.java | 492 ++++++++++-------- 1 file changed, 268 insertions(+), 224 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index 7ac93baef0..5d1f0fe8dd 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -100,7 +100,6 @@ import androidx.drawerlayout.widget.DrawerLayout; import androidx.core.content.ContextCompat; - import android.provider.Settings; import android.text.Html; import android.text.SpannableString; @@ -181,7 +180,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene @Override protected void onCreate(Bundle savedInstanceState) { - // setTheme(R.style.AppTheme); + // setTheme(R.style.AppTheme); SharedPreferences theme_prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE); int savedMode = theme_prefs.getInt(KEY_THEME_MODE, AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM); @@ -206,7 +205,8 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene KMManager.setDebugMode(true); } - // Verify WebView installed and enabled before attempting to initialize KMManager + // Verify WebView installed and enabled before attempting to initialize + // KMManager KMManager.initialize(getApplicationContext(), KeyboardType.KEYBOARD_TYPE_INAPP); KMManager.executeResourceUpdate(this); @@ -214,7 +214,8 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene DefaultLanguageResource.install(context); SharedPreferences prefs = getSharedPreferences(getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); - KMManager.SpacebarText spacebarText = KMManager.SpacebarText.fromString(prefs.getString(KeymanSettingsActivity.spacebarTextKey, KMManager.SpacebarText.LANGUAGE_KEYBOARD.toString())); + KMManager.SpacebarText spacebarText = KMManager.SpacebarText.fromString( + prefs.getString(KeymanSettingsActivity.spacebarTextKey, KMManager.SpacebarText.LANGUAGE_KEYBOARD.toString())); KMManager.setSpacebarText(spacebarText); checkHapticFeedback(); @@ -227,9 +228,9 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene navView.getLayoutParams().width = width; navView.requestLayout(); - constraintLayout = (ConstraintLayout)findViewById(R.id.constraintLayout); -// setupEdgeToEdge(R.id.constraintLayout); -// setupStatusBarColors(android.R.color.white, R.color.neutral_2); + constraintLayout = (ConstraintLayout) findViewById(R.id.constraintLayout); + // setupEdgeToEdge(R.id.constraintLayout); + // setupStatusBarColors(android.R.color.white, R.color.neutral_2); // START OF FIXED STATUS BAR LOGIC if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { Window window = getWindow(); @@ -244,7 +245,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene // Check if the system is currently in Night Mode boolean isDarkMode = (getResources().getConfiguration().uiMode & - Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES; + Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES; if (!isDarkMode) { // LIGHT MODE: Make icons BLACK so they show up on white @@ -257,7 +258,6 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } } - toolbar = (Toolbar) findViewById(R.id.titlebar); setSupportActionBar(toolbar); getSupportActionBar().setTitle(null); @@ -265,14 +265,14 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene getSupportActionBar().setDisplayUseLogoEnabled(true); getSupportActionBar().setDisplayShowHomeEnabled(true); getSupportActionBar().setDisplayShowTitleEnabled(true); -// ImageView logo = new ImageView(this); -// logo.setImageResource(R.drawable.keyman_logo); -// Toolbar.LayoutParams params = new Toolbar.LayoutParams( -// Toolbar.LayoutParams.WRAP_CONTENT, -// Toolbar.LayoutParams.WRAP_CONTENT, -// Gravity.CENTER -// ); -// toolbar.addView(logo, params); + // ImageView logo = new ImageView(this); + // logo.setImageResource(R.drawable.keyman_logo); + // Toolbar.LayoutParams params = new Toolbar.LayoutParams( + // Toolbar.LayoutParams.WRAP_CONTENT, + // Toolbar.LayoutParams.WRAP_CONTENT, + // Gravity.CENTER + // ); + // toolbar.addView(logo, params); getSupportActionBar().setLogo(R.drawable.keyman_logo); getSupportActionBar().setDisplayUseLogoEnabled(false); @@ -281,15 +281,16 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene getSupportActionBar().setDisplayUseLogoEnabled(true); getSupportActionBar().setDisplayShowHomeEnabled(true); getSupportActionBar().setLogo(R.drawable.keyman_logo_mode); -// getSupportActionBar().setLogo(R.drawable.keyman_logo); + // getSupportActionBar().setLogo(R.drawable.keyman_logo); getSupportActionBar().setDisplayShowTitleEnabled(false); getSupportActionBar().setBackgroundDrawable(getActionBarDrawable(this)); drawerLayout = findViewById(R.id.drawer_layout); drawerLayout.setScrimColor(Color.parseColor("#80000000")); - // Initialize drawerToggle WITHOUT toolbar to prevent it from showing up at the start - drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, R.string.drawer_open , R.string.drawer_close); + // Initialize drawerToggle WITHOUT toolbar to prevent it from showing up at the + // start + drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, R.string.drawer_open, R.string.drawer_close); drawerLayout.addDrawerListener(drawerToggle); drawerToggle.syncState(); @@ -299,19 +300,31 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene @Override public boolean onNavigationItemSelected(@NonNull MenuItem item) { int id = item.getItemId(); - if (id == R.id.nav_toggle_show_osk || id == R.id.action_get_started || id == R.id.nav_toggle_send_crash_report) { + if (id == R.id.nav_toggle_show_osk || id == R.id.action_get_started + || id == R.id.nav_toggle_send_crash_report) { toggleDrawerSwitch(navigationView, id); } else if (id == R.id.nav_checkbox_enable_system_keyboard) { drawerLayout.closeDrawers(); startActivity(new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS)); } else if (id == R.id.nav_checkbox_set_default_keyboard) { + drawerLayout.closeDrawers(); showDefaultKeyboardPickerFromMainPage(); - } - else if (item.getItemId() == R.id.action_settings) { - showSettings(); - return true;} - else if (id == R.id.nav_installed_languages) { + } else if (id == R.id.action_update_keyboards) { + drawerLayout.closeDrawers(); + KMManager.getUpdateTool().executeOpenUpdates(); + // Dismiss icon + updateUpdateCountIndicator(0); + final MenuItem _keyboardupdate = navigationView.getMenu().findItem(R.id.action_update_keyboards); + if (_keyboardupdate != null && _keyboardupdate.isVisible()) { + _keyboardupdate.setVisible(false); + } + + return true; + } else if (item.getItemId() == R.id.action_settings) { + showSettings(); + return true; + } else if (id == R.id.nav_installed_languages) { drawerLayout.closeDrawers(); Intent intent = new Intent(context, LanguagesSettingsActivity.class); intent.putExtra(KMManager.KMKey_DisplayKeyboardSwitcher, false); @@ -333,17 +346,17 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene // Same entries as in KeymanSettingsFragment CharSequence[] entries = { - getString(R.string.spacebar_caption_language), - getString(R.string.spacebar_caption_keyboard), - getString(R.string.spacebar_caption_language_keyboard), - getString(R.string.spacebar_caption_blank) + getString(R.string.spacebar_caption_language), + getString(R.string.spacebar_caption_keyboard), + getString(R.string.spacebar_caption_language_keyboard), + getString(R.string.spacebar_caption_blank) }; KMManager.SpacebarText[] values = { - KMManager.SpacebarText.LANGUAGE, - KMManager.SpacebarText.KEYBOARD, - KMManager.SpacebarText.LANGUAGE_KEYBOARD, - KMManager.SpacebarText.BLANK + KMManager.SpacebarText.LANGUAGE, + KMManager.SpacebarText.KEYBOARD, + KMManager.SpacebarText.LANGUAGE_KEYBOARD, + KMManager.SpacebarText.BLANK }; // Find the currently selected index @@ -351,29 +364,28 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene int currentIndex = Arrays.asList(values).indexOf(current); new AlertDialog.Builder(context) - .setTitle(getString(R.string.spacebar_caption)) - .setSingleChoiceItems(entries, currentIndex, (dialog, which) -> { - KMManager.setSpacebarText(values[which]); + .setTitle(getString(R.string.spacebar_caption)) + .setSingleChoiceItems(entries, currentIndex, (dialog, which) -> { + KMManager.setSpacebarText(values[which]); - // Persist the selection just like KeymanSettingsFragment does - SharedPreferences prefs = getSharedPreferences( - getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); - prefs.edit() - .putString(KeymanSettingsActivity.spacebarTextKey, values[which].toString()) - .apply(); + // Persist the selection just like KeymanSettingsFragment does + SharedPreferences prefs = getSharedPreferences( + getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); + prefs.edit() + .putString(KeymanSettingsActivity.spacebarTextKey, values[which].toString()) + .apply(); - dialog.dismiss(); - }) - .setNegativeButton(getString(R.string.label_cancel), null) - .show(); -// } else if (id == R.id.nav_settings) { -// drawerLayout.closeDrawers(); -// startActivity(new Intent(context, KeymanSettingsActivity.class)); - } else if (id == R.id.nav_help - ) { + dialog.dismiss(); + }) + .setNegativeButton(getString(R.string.label_cancel), null) + .show(); + // } else if (id == R.id.nav_settings) { + // drawerLayout.closeDrawers(); + // startActivity(new Intent(context, KeymanSettingsActivity.class)); + } else if (id == R.id.nav_help) { drawerLayout.closeDrawers(); startActivity(new Intent(context, InfoActivity.class)); - }else if (id == R.id.nav_palette){ + } else if (id == R.id.nav_palette) { drawerLayout.closeDrawers(); openThemeDialog(); } else if (id == R.id.nav_about_current_keyboard) { @@ -396,6 +408,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene KMManager.hideSystemKeyboard(); textView.dismissKeyboard(); } + @Override public void onDrawerOpened(View drawerView) { if (navigationView != null) { @@ -405,7 +418,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene @Override public void onDrawerClosed(View drawerView) { - textView.callOnClick(); + textView.callOnClick(); } }); @@ -431,15 +444,18 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } private void openThemeDialog() { - String[] options = {"Dark Mode", "Light Mode", "System Default"}; + String[] options = { "Dark Mode", "Light Mode", "System Default" }; SharedPreferences themePrefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE); int currentMode = themePrefs.getInt(KEY_THEME_MODE, AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM); int checkedItem; - if (currentMode == AppCompatDelegate.MODE_NIGHT_YES) checkedItem = 0; - else if (currentMode == AppCompatDelegate.MODE_NIGHT_NO) checkedItem = 1; - else checkedItem = 2; + if (currentMode == AppCompatDelegate.MODE_NIGHT_YES) + checkedItem = 0; + else if (currentMode == AppCompatDelegate.MODE_NIGHT_NO) + checkedItem = 1; + else + checkedItem = 2; AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle("Choose Theme"); @@ -447,9 +463,15 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene builder.setSingleChoiceItems(options, checkedItem, (dialog, which) -> { int modeToApply; switch (which) { - case 0: modeToApply = AppCompatDelegate.MODE_NIGHT_YES; break; - case 1: modeToApply = AppCompatDelegate.MODE_NIGHT_NO; break; - default: modeToApply = AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM; break; + case 0: + modeToApply = AppCompatDelegate.MODE_NIGHT_YES; + break; + case 1: + modeToApply = AppCompatDelegate.MODE_NIGHT_NO; + break; + default: + modeToApply = AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM; + break; } // Save the choice themePrefs.edit().putInt(KEY_THEME_MODE, modeToApply).apply(); @@ -488,13 +510,13 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene inputStream.close(); } else if (FileUtils.hasJavaScriptExtension(filename) || FileUtils.hasFontExtension(filename)) { File packagesDir = new File(getDir("data", Context.MODE_PRIVATE) + File.separator + - KMManager.KMDefault_AssetPackages); + KMManager.KMDefault_AssetPackages); if (!packagesDir.exists()) { packagesDir.mkdir(); } File undefinedPackageDir = new File(getDir("data", Context.MODE_PRIVATE) + - File.separator + KMManager.KMDefault_UndefinedPackageID); + File.separator + KMManager.KMDefault_UndefinedPackageID); if (!undefinedPackageDir.exists()) { undefinedPackageDir.mkdir(); } @@ -530,9 +552,11 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene // Reset keyboard picker Activity Task flag KMManager.closeParentAppOnShowKeyboardPicker(); - // onConfigurationChanged() only triggers when device is rotated while app is in foreground + // onConfigurationChanged() only triggers when device is rotated while app is in + // foreground // This handles when device is rotated while app is in background - // using KMManager.getOrientation() since getConfiguration().orientation is unreliable #10241 + // using KMManager.getOrientation() since getConfiguration().orientation is + // unreliable #10241 int newOrientation = KMManager.getOrientation(context); if (newOrientation != lastOrientation) { lastOrientation = newOrientation; @@ -566,8 +590,8 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene CheckPermissions.requestPermission(this, context); } break; - case "http" : - case "https" : + case "http": + case "https": // Might need to modify link like KMPBrowserActivity if (KMPLink.isKeymanInstallLink(link)) { loadingIntentUri = KMPLink.getKeyboardDownloadLink(link); @@ -576,17 +600,18 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene KMLog.LogBreadcrumb(TAG, "Installing via HTTPS intent: " + link, true); downloadKMP(loadingIntentUri, KmpInstallMode.Full); break; - case "keyman" : - // TODO: Only accept download links from Keyman browser activities when universal links work + case "keyman": + // TODO: Only accept download links from Keyman browser activities when + // universal links work KMLog.LogBreadcrumb(TAG, "Installing via keyman-link intent: " + link, true); if (KMPLink.isKeymanDownloadLink(loadingIntentUri.toString())) { // Convert opaque URI to hierarchical URI so the query parameters can be parsed Builder builder = new Uri.Builder(); builder.scheme("https") - .authority("keyman.com") - .appendPath("keyboards") - .encodedQuery(loadingIntentUri.getEncodedQuery()); + .authority("keyman.com") + .appendPath("keyboards") + .encodedQuery(loadingIntentUri.getEncodedQuery()); loadingIntentUri = Uri.parse(builder.build().toString()); downloadKMP(loadingIntentUri, KmpInstallMode.Full); } else if (KMPLink.isLegacyKeymanDownloadLink(loadingIntentUri.toString())) { @@ -597,7 +622,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene KMLog.LogError(TAG, msg); } break; - default : + default: String msg = "Unrecognized scheme: " + scheme; KMLog.LogError(TAG, msg); } @@ -638,26 +663,26 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } @SuppressLint("RestrictedApi") -// @Override -// public boolean onPrepareOptionsMenu(final Menu menu) { -// this.menu = menu; -// final MenuItem _overflowMenuItem = menu.findItem(R.id.action_overflow); -// if (_overflowMenuItem != null) { -// MenuItem updateKeyboards = this.menu.findItem(R.id.action_update_keyboards); -// updateUpdateCountIndicator(updateKeyboards, -// KMManager.getUpdateTool().getOpenUpdateCount(), true); -// } -// return super.onPrepareOptionsMenu(menu); -// } + // @Override + // public boolean onPrepareOptionsMenu(final Menu menu) { + // this.menu = menu; + // final MenuItem _overflowMenuItem = menu.findItem(R.id.action_overflow); + // if (_overflowMenuItem != null) { + // MenuItem updateKeyboards = this.menu.findItem(R.id.action_update_keyboards); + // updateUpdateCountIndicator(updateKeyboards, + // KMManager.getUpdateTool().getOpenUpdateCount(), true); + // } + // return super.onPrepareOptionsMenu(menu); + // } private void updateUpdateCountIndicator(int anUpdateCount) { if (menu == null) { return; } -// final MenuItem _overflowMenuItem = menu.findItem(R.id.action_overflow); -// if (_overflowMenuItem != null) { -// updateUpdateCountIndicator(_overflowMenuItem, anUpdateCount, false); -// } + // final MenuItem _overflowMenuItem = menu.findItem(R.id.action_overflow); + // if (_overflowMenuItem != null) { + // updateUpdateCountIndicator(_overflowMenuItem, anUpdateCount, false); + // } final MenuItem _keyboardupdate = menu.findItem(R.id.action_update_keyboards); if (_keyboardupdate != null && anUpdateCount > 0) { @@ -665,25 +690,24 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } } - private void updateUpdateCountIndicator(MenuItem theItem, int anUpdateCount, boolean aHideMenuitem) - { + private void updateUpdateCountIndicator(MenuItem theItem, int anUpdateCount, boolean aHideMenuitem) { final ViewGroup _rootView = (ViewGroup) theItem.getActionView(); - if(anUpdateCount==0) { + if (anUpdateCount == 0) { if (aHideMenuitem) { theItem.setVisible(false); } else if (_rootView != null) { _rootView.findViewById(R.id.update_count_indicator).setVisibility(View.GONE); } } else { - if(aHideMenuitem) { + if (aHideMenuitem) { theItem.setVisible(true); - } else if(_rootView!=null) { + } else if (_rootView != null) { _rootView.findViewById(R.id.update_count_indicator).setVisibility(View.VISIBLE); } } - if(_rootView==null) { + if (_rootView == null) { return; } @@ -697,39 +721,39 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene getMenuInflater().inflate(R.menu.main, menu); this.menu = menu; - KMManager.getUpdateTool().addPropertyChangeListener(new PropertyChangeListener() - { - @Override - public void propertyChange(PropertyChangeEvent evt) { - if(!evt.getPropertyName().equals("updateCount")) - return; - updateUpdateCountIndicator( - evt.getNewValue()==null?0:(Integer) evt.getNewValue()); - } + KMManager.getUpdateTool().addPropertyChangeListener(new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (!evt.getPropertyName().equals("updateCount")) + return; + updateUpdateCountIndicator( + evt.getNewValue() == null ? 0 : (Integer) evt.getNewValue()); } - ); + }); updateUpdateCountIndicator(KMManager.getUpdateTool().getOpenUpdateCount()); return true; } - @Override public boolean onOptionsItemSelected(MenuItem item) { - // Android Gradle 8.0 no longer declares resources final, so can't use switch statement here + // Android Gradle 8.0 no longer declares resources final, so can't use switch + // statement here if (item.getItemId() == R.id.action_info) { showInfo(); return true; } else if (item.getItemId() == R.id.action_share) { showShareDialog(); return true; - /* Disable Web Browser to investigate Google sign-in - } else if ((item.getItemId() == R.id.action_web) { - showWebBrowser(); - return true;*/ + /* + * Disable Web Browser to investigate Google sign-in + * } else if ((item.getItemId() == R.id.action_web) { + * showWebBrowser(); + * return true; + */ } else if (item.getItemId() == R.id.action_text_size) { showTextSizeDialog(); return true; - } else if (item.getItemId() == R.id.action_clear_text){ + } else if (item.getItemId() == R.id.action_clear_text) { showClearTextDialog(); return true; } else if (item.getItemId() == R.id.action_get_started) { @@ -928,33 +952,39 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } public void downloadKMP(String packageId, String bcp47, KmpInstallMode installMode) { - Uri downloadUri = bcp47 == null ? - Uri.parse(KMString.format("https://keyman.com/go/package/download/%s", new Object[]{packageId})) : - Uri.parse(KMString.format("https://keyman.com/go/package/download/%s?bcp47=%s", new Object[]{packageId, bcp47})); + Uri downloadUri = bcp47 == null + ? Uri.parse(KMString.format("https://keyman.com/go/package/download/%s", new Object[] { packageId })) + : Uri.parse( + KMString.format("https://keyman.com/go/package/download/%s?bcp47=%s", new Object[] { packageId, bcp47 })); downloadKMP(downloadUri, installMode); } /** - * Parse the URI data to determine the filename and URL for the .kmp keyboard package. + * Parse the URI data to determine the filename and URL for the .kmp keyboard + * package. * If URL is valid, download the kmp. - * @param packageUri URI to download the package. + * + * @param packageUri URI to download the package. * @param installMode KMP installation mode (silent, welcome only, or full) - * TODO: only ever pass packageId and bcp47 from callers, as KMPLink should be responsible for - * URL parsing, not this function. + * TODO: only ever pass packageId and bcp47 from callers, as + * KMPLink should be responsible for + * URL parsing, not this function. */ public void downloadKMP(Uri packageUri, KmpInstallMode installMode) { if (packageUri == null) { - KMLog.LogError(TAG,"null uri passed to downloadKmp"); + KMLog.LogError(TAG, "null uri passed to downloadKmp"); String message = "Download failed. Invalid URL."; Toast.makeText(getApplicationContext(), message, - Toast.LENGTH_SHORT).show(); + Toast.LENGTH_SHORT).show(); return; } try { - // Initial try with Keyman 13.0 download link, format keyman://localhost/install?url=... + // Initial try with Keyman 13.0 download link, format + // keyman://localhost/install?url=... String url = packageUri.getQueryParameter(KMKeyboardDownloaderActivity.KMKey_URL); if (url == null) { - // Otherwise, Keyman 14+ download link is format https://keyman.com/go/package/download/[?bcp47=][&...] + // Otherwise, Keyman 14+ download link is format + // https://keyman.com/go/package/download/[?bcp47=][&...] url = packageUri.toString(); } if (url != null) { @@ -970,7 +1000,8 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene // Parse query for the BCP 47 language ID String languageID = packageUri.getQueryParameter(KMKeyboardDownloaderActivity.KMKey_BCP47); - // TODO: Using "tag" for now, but production will be KMKeyboardDownloaderActivity.KMKey_BCP47 + // TODO: Using "tag" for now, but production will be + // KMKeyboardDownloaderActivity.KMKey_BCP47 if (languageID == null) { languageID = packageUri.getQueryParameter("tag"); } @@ -1004,18 +1035,18 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } catch (Exception e) { KMLog.LogException(TAG, "", e); cleanupPackageInstall(); - return;//break; + return;// break; } } else { String message = "Download failed. Not a .kmp keyboard package."; Toast.makeText(getApplicationContext(), message, - Toast.LENGTH_SHORT).show(); + Toast.LENGTH_SHORT).show(); } } catch (UnsupportedOperationException e) { String message = "Download failed. Invalid URL."; - KMLog.LogException(TAG, message, e); + KMLog.LogException(TAG, message, e); Toast.makeText(getApplicationContext(), message, - Toast.LENGTH_SHORT).show(); + Toast.LENGTH_SHORT).show(); cleanupPackageInstall(); } } @@ -1035,7 +1066,8 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene // Status bar height only used for portrait orientation int statusBarHeight = 0; if (lastOrientation == Configuration.ORIENTATION_PORTRAIT) { - // *** TO DO: Try to check if status bar is visible, set statusBarHeight to 0 if it is not visible *** + // *** TO DO: Try to check if status bar is visible, set statusBarHeight to 0 if + // it is not visible *** int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android"); if (resourceId > 0) { statusBarHeight = getResources().getDimensionPixelSize(resourceId); @@ -1046,7 +1078,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene Point size = KMManager.getWindowSize(context); int screenHeight = size.y; textView.setHeight( - screenHeight - statusBarHeight - actionBarHeight - bannerHeight - keyboardHeight - navigationBarHeight); + screenHeight - statusBarHeight - actionBarHeight - bannerHeight - keyboardHeight - navigationBarHeight); } private void showInfo() { @@ -1069,8 +1101,10 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene private void showShareDialog() { // Sharing text content via send intent - // Reference: https://developer.android.com/training/sharing/send#send-text-content - // Note: Sharing to Facebook removed in https://github.com/keymanapp/keyman/issues/156 + // Reference: + // https://developer.android.com/training/sharing/send#send-text-content + // Note: Sharing to Facebook removed in + // https://github.com/keymanapp/keyman/issues/156 // Users can copy text or use Keyman as system keyboard for typing into Facebook Intent sendIntent = new Intent(Intent.ACTION_SEND); sendIntent.setType("text/plain"); @@ -1145,10 +1179,12 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene /** * Combine a localized string for "Text Size" plus Arabic numerals + * * @return String */ private String getTextSizeString() { - // Instead of formatting the number, will truncate formatting and concat the actual textSize + // Instead of formatting the number, will truncate formatting and concat the + // actual textSize String label = getString(R.string.text_size).replace("%1$d", ""); return label + KMString.format(" %d", textSize); } @@ -1172,7 +1208,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } }); - //dialogBuilder.show(); no dialog + // dialogBuilder.show(); no dialog final AlertDialog dialog = dialogBuilder.create(); dialog.show(); dialog.getWindow().setBackgroundDrawableResource(R.color.dialogBackground); @@ -1213,6 +1249,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene /** * Based on whether Chrome and WebView are installed & enabled, * display the corresponding update panel + * * @param chromeStatus * @param webViewStatus */ @@ -1232,10 +1269,10 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } private void displayInstallWebView() { - TextView textView = (TextView)findViewById((R.id.kmWebViewChromeTextView)); + TextView textView = (TextView) findViewById((R.id.kmWebViewChromeTextView)); textView.setText(getString(R.string.body_install_webview)); - Button button = (Button)findViewById(R.id.webViewChromeButton); + Button button = (Button) findViewById(R.id.webViewChromeButton); button.setText(getString(R.string.label_install_webview)); button.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { @@ -1250,7 +1287,8 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene startActivity(intent); } } catch (android.content.ActivityNotFoundException e) { - Toast.makeText(getApplicationContext(), getString(R.string.unable_to_open_browser), Toast.LENGTH_SHORT).show(); + Toast.makeText(getApplicationContext(), getString(R.string.unable_to_open_browser), Toast.LENGTH_SHORT) + .show(); } } }); @@ -1260,16 +1298,16 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } private void displayEnableWebView() { - TextView textView = (TextView)findViewById((R.id.kmWebViewChromeTextView)); + TextView textView = (TextView) findViewById((R.id.kmWebViewChromeTextView)); textView.setText(getString(R.string.body_enable_webview)); - Button button = (Button)findViewById(R.id.webViewChromeButton); + Button button = (Button) findViewById(R.id.webViewChromeButton); button.setText(getString(R.string.label_enable_webview)); button.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { // Application settings to enable WebView Intent intent = new Intent("android.settings.APPLICATION_DETAILS_SETTINGS", - Uri.parse("package:com.google.android.webview")); + Uri.parse("package:com.google.android.webview")); // Launch intent try { @@ -1278,7 +1316,8 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene startActivity(intent); } } catch (android.content.ActivityNotFoundException e) { - Toast.makeText(getApplicationContext(), getString(R.string.unable_to_open_browser), Toast.LENGTH_SHORT).show(); + Toast.makeText(getApplicationContext(), getString(R.string.unable_to_open_browser), Toast.LENGTH_SHORT) + .show(); } } }); @@ -1289,17 +1328,17 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene private void displayUpdateChrome() { // TextView's default string is to update Chrome - TextView textView = (TextView)findViewById(R.id.kmWebViewChromeTextView); + TextView textView = (TextView) findViewById(R.id.kmWebViewChromeTextView); textView.setText(String.format(getString(R.string.text_require_chrome_version), - WebViewUtils.KEYMAN_MIN_TARGET_VERSION_ANDROID_CHROME)); + WebViewUtils.KEYMAN_MIN_TARGET_VERSION_ANDROID_CHROME)); - Button button = (Button)findViewById(R.id.webViewChromeButton); + Button button = (Button) findViewById(R.id.webViewChromeButton); button.setText(getString(R.string.button_update_chrome)); button.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { // Primary Play Store link to install/update Chrome Intent intent = new Intent(Intent.ACTION_VIEW, - Uri.parse("market://details?id=com.android.chrome")); + Uri.parse("market://details?id=com.android.chrome")); try { PackageManager packageManager = context.getPackageManager(); @@ -1308,13 +1347,14 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } else { // (alternate Play Store link to install/update Chrome) intent = new Intent(Intent.ACTION_VIEW, - Uri.parse("https://play.google.com/store/apps/details?id=com.android.chrome")); + Uri.parse("https://play.google.com/store/apps/details?id=com.android.chrome")); intent.resolveActivity(packageManager); startActivity(intent); } } catch (android.content.ActivityNotFoundException e) { - Toast.makeText(getApplicationContext(), getString(R.string.unable_to_open_browser), Toast.LENGTH_SHORT).show(); + Toast.makeText(getApplicationContext(), getString(R.string.unable_to_open_browser), Toast.LENGTH_SHORT) + .show(); } } }); @@ -1361,7 +1401,8 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene return; } else if (!isKMP) { String noKeyboardsInstalledMessage = String.format( - context.getString(R.string.not_valid_package_file), filename, context.getString(R.string.no_targets_to_install)); + context.getString(R.string.not_valid_package_file), filename, + context.getString(R.string.no_targets_to_install)); Toast.makeText(context, noKeyboardsInstalledMessage, Toast.LENGTH_LONG).show(); return; } @@ -1391,52 +1432,52 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene SharedPreferences prefs = getSharedPreferences(getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); bindDrawerSwitch(navigationView, R.id.nav_toggle_show_osk, - prefs.getBoolean(KeymanSettingsActivity.oskWithPhysicalKeyboardKey, false), - new SwitchChangeHandler() { - @Override - public void onChanged(boolean isChecked) { - SharedPreferences.Editor editor = prefs.edit(); - editor.putBoolean(KeymanSettingsActivity.oskWithPhysicalKeyboardKey, isChecked); - editor.apply(); - } - }); + prefs.getBoolean(KeymanSettingsActivity.oskWithPhysicalKeyboardKey, false), + new SwitchChangeHandler() { + @Override + public void onChanged(boolean isChecked) { + SharedPreferences.Editor editor = prefs.edit(); + editor.putBoolean(KeymanSettingsActivity.oskWithPhysicalKeyboardKey, isChecked); + editor.apply(); + } + }); bindDrawerSwitch(navigationView, R.id.action_get_started, - prefs.getBoolean(GetStartedActivity.showGetStartedKey, true), - new SwitchChangeHandler() { - @Override - public void onChanged(boolean isChecked) { - SharedPreferences.Editor editor = prefs.edit(); - editor.putBoolean(GetStartedActivity.showGetStartedKey, isChecked); - editor.apply(); - } - }); + prefs.getBoolean(GetStartedActivity.showGetStartedKey, true), + new SwitchChangeHandler() { + @Override + public void onChanged(boolean isChecked) { + SharedPreferences.Editor editor = prefs.edit(); + editor.putBoolean(GetStartedActivity.showGetStartedKey, isChecked); + editor.apply(); + } + }); bindDrawerSwitch(navigationView, R.id.nav_toggle_send_crash_report, - prefs.getBoolean(KeymanSettingsActivity.sendCrashReport, true), - new SwitchChangeHandler() { - @Override - public void onChanged(boolean isChecked) { - SharedPreferences.Editor editor = prefs.edit(); - editor.putBoolean(KeymanSettingsActivity.sendCrashReport, isChecked); - editor.apply(); - KMManager.setMaySendCrashReport(isChecked); - } - }); + prefs.getBoolean(KeymanSettingsActivity.sendCrashReport, true), + new SwitchChangeHandler() { + @Override + public void onChanged(boolean isChecked) { + SharedPreferences.Editor editor = prefs.edit(); + editor.putBoolean(KeymanSettingsActivity.sendCrashReport, isChecked); + editor.apply(); + KMManager.setMaySendCrashReport(isChecked); + } + }); } private void initializeDrawerItemSubtitles(NavigationView navigationView) { setDrawerItemSubtitle(navigationView, R.id.nav_spacebar_caption, - getString(R.string.drawer_subtitle_spacebar_caption)); + getString(R.string.drawer_subtitle_spacebar_caption)); setDrawerItemSubtitle(navigationView, R.id.nav_toggle_show_osk, - getString(R.string.drawer_subtitle_show_osk)); + getString(R.string.drawer_subtitle_show_osk)); setDrawerItemSubtitle(navigationView, R.id.nav_toggle_send_crash_report, - getString(R.string.drawer_subtitle_send_crash_report)); + getString(R.string.drawer_subtitle_send_crash_report)); setDrawerItemSubtitle(navigationView, R.id.nav_help, - getString(R.string.drawer_subtitle_about)); + getString(R.string.drawer_subtitle_about)); setDrawerItemSubtitle(navigationView, R.id.nav_about_current_keyboard, - getString(R.string.drawer_subtitle_about_current_keyboard)); + getString(R.string.drawer_subtitle_about_current_keyboard)); } private void updateCurrentKeyboardDrawerItemTitle(NavigationView navigationView) { @@ -1447,10 +1488,12 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene Keyboard currentKeyboard = getBestAvailableKeyboard(); String newTitle; - if (currentKeyboard != null && currentKeyboard.getKeyboardName() != null && !currentKeyboard.getKeyboardName().isEmpty()) { + if (currentKeyboard != null && currentKeyboard.getKeyboardName() != null + && !currentKeyboard.getKeyboardName().isEmpty()) { newTitle = getString(R.string.drawer_about_current_keyboard) + ": " + currentKeyboard.getKeyboardName(); } else { - newTitle = getString(R.string.drawer_about_current_keyboard) + ": " + getString(R.string.drawer_about_no_active_keyboard); + newTitle = getString(R.string.drawer_about_current_keyboard) + ": " + + getString(R.string.drawer_about_no_active_keyboard); } if (!newTitle.equals(lastCurrentKeyboardDrawerTitle)) { @@ -1529,30 +1572,30 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene SpannableString fullTitle = new SpannableString(baseTitle + "\n" + subtitle); int subtitleStart = baseTitle.length() + 1; fullTitle.setSpan(new RelativeSizeSpan(0.85f), subtitleStart, fullTitle.length(), - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); TypedValue typedValue = new TypedValue(); getTheme().resolveAttribute(android.R.attr.textColorSecondary, typedValue, true); int subtitleColor = typedValue.resourceId != 0 - ? ContextCompat.getColor(this, typedValue.resourceId) - : typedValue.data; + ? ContextCompat.getColor(this, typedValue.resourceId) + : typedValue.data; fullTitle.setSpan(new ForegroundColorSpan(subtitleColor), subtitleStart, fullTitle.length(), - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); menuItem.setTitle(fullTitle); } private void initializeDrawerCheckboxOptions(NavigationView navigationView) { bindDrawerCheckboxAction(navigationView, R.id.nav_checkbox_enable_system_keyboard, - () -> { - if (drawerLayout != null) { - drawerLayout.closeDrawers(); - } - startActivity(new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS)); - }); + () -> { + if (drawerLayout != null) { + drawerLayout.closeDrawers(); + } + startActivity(new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS)); + }); bindDrawerCheckboxAction(navigationView, R.id.nav_checkbox_set_default_keyboard, - this::showDefaultKeyboardPickerFromMainPage); + this::showDefaultKeyboardPickerFromMainPage); } private void showDefaultKeyboardPickerFromMainPage() { @@ -1563,13 +1606,13 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene private void refreshDrawerSystemKeyboardCheckboxes(NavigationView navigationView) { setDrawerCheckboxState(navigationView, R.id.nav_checkbox_enable_system_keyboard, - SystemIMESettings.isEnabledAsSystemKB(context)); + SystemIMESettings.isEnabledAsSystemKB(context)); setDrawerCheckboxState(navigationView, R.id.nav_checkbox_set_default_keyboard, - SystemIMESettings.isDefaultKB(context)); + SystemIMESettings.isDefaultKB(context)); } private void bindDrawerSwitch(NavigationView navigationView, int menuItemId, boolean defaultValue, - final SwitchChangeHandler switchChangeHandler) { + final SwitchChangeHandler switchChangeHandler) { MenuItem menuItem = navigationView.getMenu().findItem(menuItemId); if (menuItem == null) { return; @@ -1624,7 +1667,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } private void bindDrawerCheckboxAction(NavigationView navigationView, int menuItemId, - final Runnable onActivate) { + final Runnable onActivate) { MenuItem menuItem = navigationView.getMenu().findItem(menuItemId); if (menuItem == null) { return; @@ -1695,7 +1738,8 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene public static void cleanupPackageInstall() { if (progressDialog != null && progressDialog.isShowing()) { progressDialog.dismiss(); - }; + } + ; progressDialog = null; } @@ -1718,8 +1762,8 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene Paint p = new Paint(); p.setStyle(Paint.Style.FILL); - //p.setColor(Color.BLACK); - p.setColor(ContextCompat.getColor(context,R.color.toolbarColor)); + // p.setColor(Color.BLACK); + p.setColor(ContextCompat.getColor(context, R.color.toolbarColor)); canvas.drawRect(new Rect(0, 0, width, actionBarHeight), p); p.setColor(context.getResources().getColor(R.color.keyman_orange)); @@ -1754,21 +1798,21 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene HashMap hashMap = new HashMap<>(keyboardsInstalled.get(i)); String languageID = hashMap.get(KMManager.KMKey_LanguageID); Keyboard keyboardInfo = new Keyboard( - hashMap.get(KMManager.KMKey_PackageID), - hashMap.get(KMManager.KMKey_KeyboardID), - hashMap.get(KMManager.KMKey_KeyboardName), - languageID, - hashMap.get(KMManager.KMKey_LanguageName), - hashMap.get(KMManager.KMKey_Version), - hashMap.get(KMManager.KMKey_CustomHelpLink), - hashMap.get(KMManager.KMKey_KMPLink), - true, - hashMap.get(KMManager.KMKey_Font), - hashMap.get(KMManager.KMKey_OskFont)); + hashMap.get(KMManager.KMKey_PackageID), + hashMap.get(KMManager.KMKey_KeyboardID), + hashMap.get(KMManager.KMKey_KeyboardName), + languageID, + hashMap.get(KMManager.KMKey_LanguageName), + hashMap.get(KMManager.KMKey_Version), + hashMap.get(KMManager.KMKey_CustomHelpLink), + hashMap.get(KMManager.KMKey_KMPLink), + true, + hashMap.get(KMManager.KMKey_Font), + hashMap.get(KMManager.KMKey_OskFont)); - if (hashMap.containsKey(KMManager.KMKey_KMPInstall_Mode)) { - installMode = KmpInstallMode.fromString(hashMap.get(KMManager.KMKey_KMPInstall_Mode)); - } + if (hashMap.containsKey(KMManager.KMKey_KMPInstall_Mode)) { + installMode = KmpInstallMode.fromString(hashMap.get(KMManager.KMKey_KMPInstall_Mode)); + } if (i == 0) { packageID = keyboardInfo.getPackageID(); customHelpLink = keyboardInfo.getHelpLink(); @@ -1796,11 +1840,11 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene ArrayList aPreparedCloudApiParams = new ArrayList<>(); String url = CloudRepository.prepareLexicalModelQuery(languageID); aPreparedCloudApiParams.add(new CloudApiTypes.CloudApiParam( - CloudApiTypes.ApiTarget.KeyboardLexicalModels, url).setType(CloudApiTypes.JSONType.Array)); + CloudApiTypes.ApiTarget.KeyboardLexicalModels, url).setType(CloudApiTypes.JSONType.Array)); CloudDownloadMgr.getInstance().executeAsDownload( - context, _downloadid, null, _callback, - aPreparedCloudApiParams.toArray(new CloudApiTypes.CloudApiParam[0])); + context, _downloadid, null, _callback, + aPreparedCloudApiParams.toArray(new CloudApiTypes.CloudApiParam[0])); } } } @@ -1822,14 +1866,14 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene public void onLexicalModelInstalled(List> lexicalModelsInstalled) { cleanupPackageInstall(); - String langId = (KMManager.getCurrentKeyboardInfo(this) != null) ? - KMManager.getCurrentKeyboardInfo(this).getLanguageID() : - KMManager.KMDefault_LanguageID; + String langId = (KMManager.getCurrentKeyboardInfo(this) != null) + ? KMManager.getCurrentKeyboardInfo(this).getLanguageID() + : KMManager.KMDefault_LanguageID; boolean matchingModel = false; - for(int i=0; ilexicalModelInfo = new HashMap<>(lexicalModelsInstalled.get(i)); - if(lexicalModelInfo.get(KMManager.KMKey_LanguageID).equals(langId)) { + for (int i = 0; i < lexicalModelsInstalled.size(); i++) { + HashMap lexicalModelInfo = new HashMap<>(lexicalModelsInstalled.get(i)); + if (lexicalModelInfo.get(KMManager.KMKey_LanguageID).equals(langId)) { matchingModel = true; } KMManager.addLexicalModel(this, lexicalModelInfo); @@ -1837,7 +1881,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene // We're on the main thread, so if the active keyboard's language code matches, // let's register the associated lexical model. - if(matchingModel) { + if (matchingModel) { KMManager.registerAssociatedLexicalModel(langId); } } From bf5164dd3a94a16f64fcf7b8690ea7c13daa81ee Mon Sep 17 00:00:00 2001 From: memaynor <78869428+memaynor@users.noreply.github.com> Date: Wed, 8 Apr 2026 13:22:20 +0700 Subject: [PATCH 035/140] Update README.md --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 84482913cf..601fdcc696 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,3 @@ Keyman is an open source project distributed under the [MIT license](LICENSE.md) * [Documentation](https://help.keyman.com/) * [Online Keyboard](https://keymanweb.com/) * [SIL Language Technology - software.sil.org](https://software.sil.org/) - - -# test \ No newline at end of file From e281b9a4332e393a0e10a00a35340e68aad7f95e Mon Sep 17 00:00:00 2001 From: Mengchou97 Date: Wed, 8 Apr 2026 14:41:29 +0700 Subject: [PATCH 036/140] updated string for osk --- android/KMAPro/kMAPro/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/KMAPro/kMAPro/src/main/res/values/strings.xml b/android/KMAPro/kMAPro/src/main/res/values/strings.xml index 910471a43c..83c966d100 100644 --- a/android/KMAPro/kMAPro/src/main/res/values/strings.xml +++ b/android/KMAPro/kMAPro/src/main/res/values/strings.xml @@ -16,7 +16,7 @@ Show keyboard name on spacebar - When a physical keyboard is connected + When physical keyboard connected When on, crash reports will be sent From 3c11eae874372cc65918dd3e2ecbe2f94440295a Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 13 Apr 2026 08:33:03 +0200 Subject: [PATCH 037/140] chore: restore files --- common/web/types/package.json | 2 - .../chromium/Keyman.UI.UframeCEFHost.pas | 789 ++++++++++++++++++ 2 files changed, 789 insertions(+), 2 deletions(-) create mode 100644 common/windows/delphi/chromium/Keyman.UI.UframeCEFHost.pas diff --git a/common/web/types/package.json b/common/web/types/package.json index 4c2e2bfd2e..2b2122f682 100644 --- a/common/web/types/package.json +++ b/common/web/types/package.json @@ -36,8 +36,6 @@ "restructure": "3.0.1" }, "devDependencies": { - "@types/mocha": "^5.2.7", - "@types/node": "^20.4.1", "ajv": "^8.12.0", "ajv-cli": "^5.0.0", "ajv-formats": "^2.1.1", diff --git a/common/windows/delphi/chromium/Keyman.UI.UframeCEFHost.pas b/common/windows/delphi/chromium/Keyman.UI.UframeCEFHost.pas new file mode 100644 index 0000000000..abc1b0f169 --- /dev/null +++ b/common/windows/delphi/chromium/Keyman.UI.UframeCEFHost.pas @@ -0,0 +1,789 @@ +unit Keyman.UI.UframeCEFHost; + +interface + +uses + System.Classes, + System.Contnrs, + System.SysUtils, + System.Types, + System.UITypes, + Vcl.Controls, + Vcl.Dialogs, + Vcl.ExtCtrls, + Vcl.Forms, + Vcl.Graphics, + Vcl.Menus, + Winapi.Messages, + Winapi.Windows, + + uCEFChromium, + uCEFChromiumCore, + uCEFChromiumEvents, + uCEFChromiumWindow, + uCEFInterfaces, + uCEFTypes, + uCEFWinControl, + uCEFWindowParent, + + Keyman.System.CEFManager, + UserMessages, + utilexecute; + +const + CEF_DESTROY = WM_USER + 300; + CEF_AFTERDESTROY = WM_USER + 301; + CEF_AFTERCREATE = WM_USER + 302; + CEF_SHOW = WM_USER + 303; + CEF_LOADEND = WM_USER + 304; + CEF_KEYEVENT = WM_USER + 306; + CEF_BEFOREBROWSE = WM_USER + 307; + CEF_TITLECHANGE = WM_USER + 309; + CEF_COMMAND = WM_USER + 310; + CEF_SETFOCUS = WM_USER + 312; + CEF_LOADINGSTATECHANGE = WM_USER + 313; + + + CEF_LOADINGSTATECHANGE_ISLOADING = $0001; + CEF_LOADINGSTATECHANGE_CANGOBACK = $0002; + CEF_LOADINGSTATECHANGE_CANGOFORWARD = $0004; + +type + TCEFHostKeyEventData = record + browserid: Integer; + event: TCefKeyEvent; + osEvent: TMsg; + end; + + PCEFHostKeyEventData = ^TCEFHostKeyEventData; + + TCEFConsoleMessageEventData = record + browserid: Integer; + level: Cardinal; + message, source: ustring; + line: Integer; + end; + + PCEFConsoleMessageEventData = ^TCEFConsoleMessageEventData; + + TCEFTitleChangeEventData = record + browserid: Integer; + title: string; + end; + + PCEFTitleChangeEventData = ^TCEFTitleChangeEventData; + + TCEFHostBeforeBrowseExSyncEvent = procedure(Sender: TObject; const Url: string; isMain, isPopup: Boolean; out Handled: Boolean) of object; + TCEFHostBeforeBrowseSyncEvent = procedure(Sender: TObject; const Url: string; isPopup: Boolean; out Handled: Boolean) of object; + TCEFHostBeforeBrowseExEvent = procedure(Sender: TObject; const Url: string; isMain, isPopup, wasHandled: Boolean) of object; + TCEFHostBeforeBrowseEvent = procedure(Sender: TObject; const Url: string; isPopup, wasHandled: Boolean) of object; + TCEFCommandEvent = procedure(Sender: TObject; const command: string; params: TStringList) of object; + + TCEFHostPreKeySyncEvent = procedure(Sender: TObject; e: TCEFHostKeyEventData; out isShortcut, Handled: Boolean) of object; + TCEFHostKeyEvent = procedure(Sender: TObject; e: TCEFHostKeyEventData; wasShortcut, wasHandled: Boolean) of object; + TCEFHostTitleChangeEvent = procedure(Sender: TObject; const title: string) of object; + TCEFHostLoadingStateChangeEvent = procedure(Sender: TObject; isLoading, canGoBack, canGoForward: Boolean) of object; + + TframeCEFHost = class(TForm, IKeymanCEFHost) + tmrRefresh: TTimer; + tmrCreateBrowser: TTimer; + cefwp: TCEFWindowParent; + cef: TChromium; + procedure FormCreate(Sender: TObject); + procedure tmrCreateBrowserTimer(Sender: TObject); + procedure FormShow(Sender: TObject); + procedure FormDestroy(Sender: TObject); + procedure cefAfterCreated(Sender: TObject; const browser: ICefBrowser); + procedure cefBeforeClose(Sender: TObject; const browser: ICefBrowser); + procedure cefClose(Sender: TObject; const browser: ICefBrowser; + var aAction: TCefCloseBrowserAction); + procedure cefPreKeyEvent(Sender: TObject; const browser: ICefBrowser; + const event: PCefKeyEvent; osEvent: TCefEventHandle; out isKeyboardShortcut, + Result: Boolean); // I2986 + + procedure cefLoadEnd(Sender: TObject; const browser: ICefBrowser; + const frame: ICefFrame; httpStatusCode: Integer); + procedure cefBeforeBrowse(Sender: TObject; const browser: ICefBrowser; + const frame: ICefFrame; const request: ICefRequest; user_gesture, + isRedirect: Boolean; out Result: Boolean); + procedure cefRunContextMenu(Sender: TObject; const browser: ICefBrowser; + const frame: ICefFrame; const params: ICefContextMenuParams; + const model: ICefMenuModel; + const callback: ICefRunContextMenuCallback; + var aResult : Boolean); + procedure cefBeforePopup(Sender: TObject; + const browser: ICefBrowser; + const frame: ICefFrame; + const targetUrl, + targetFrameName: ustring; + targetDisposition: TCefWindowOpenDisposition; + userGesture: Boolean; + const popupFeatures: TCefPopupFeatures; + var windowInfo: TCefWindowInfo; + var client: ICefClient; + var settings: TCefBrowserSettings; + var extra_info: ICefDictionaryValue; + var noJavascriptAccess: Boolean; + var Result: Boolean); + procedure cefSetFocus(Sender: TObject; const browser: ICefBrowser; + source: TCefFocusSource; out Result: Boolean); + procedure cefTitleChange(Sender: TObject; const browser: ICefBrowser; + const title: ustring); + procedure cefWidgetCompMsg(Sender: TObject; var aMessage: TMessage; var aHandled: Boolean); + procedure cefLoadingStateChange(Sender: TObject; const browser: ICefBrowser; + isLoading, canGoBack, canGoForward: Boolean); + private + FApplicationHandle: THandle; + FNextURL: string; + FOnLoadEnd: TNotifyEvent; + FOnBeforeBrowseSync: TCEFHostBeforeBrowseSyncEvent; + FOnBeforeBrowseExSync: TCEFHostBeforeBrowseExSyncEvent; + FOnAfterCreated: TNotifyEvent; + FShutdownCompletionHandler: TShutdownCompletionHandlerEvent; + FIsClosing: Boolean; + FShouldShowContextMenu: Boolean; + FShouldOpenRemoteUrlsInBrowser: Boolean; + + FCallbackWnd: THandle; + FOnPreKeySyncEvent: TCEFHostPreKeySyncEvent; + FOnKeyEvent: TCEFHostKeyEvent; + FOnBeforeBrowse: TCEFHostBeforeBrowseEvent; + FOnBeforeBrowseEx: TCEFHostBeforeBrowseExEvent; + FOnTitleChange: TCEFHostTitleChangeEvent; + FOnCommand: TCEFCommandEvent; + FOnHelpTopic: TNotifyEvent; + FIsCreated: Boolean; + FOnLoadingStateChange: TCEFHostLoadingStateChangeEvent; + + procedure CallbackWndProc(var Message: TMessage); + + // IKeymanCEFHost + procedure StartShutdown(CompletionHandler: TShutdownCompletionHandlerEvent); + function GetDebugInfo: string; + + procedure Handle_CEF_DESTROY(var Message: TMessage); + procedure Handle_CEF_AFTERDESTROY(var Message: TMessage); + procedure Handle_CEF_AFTERCREATE(var Message: TMessage); + procedure Handle_CEF_SHOW(var message: TMessage); + procedure Handle_CEF_LOADEND(var message: TMessage); + procedure Handle_CEF_KEYEVENT(var message: TMessage); + procedure Handle_CEF_BEFOREBROWSE(var message: TMessage); + procedure Handle_CEF_TITLECHANGE(var message: TMessage); + procedure Handle_CEF_COMMAND(var message: TMessage); + procedure Handle_CEF_SETFOCUS(var message: TMessage); + procedure Handle_CEF_LOADINGSTATECHANGE(var message: TMessage); + + // CEF: You have to handle this two messages to call NotifyMoveOrResizeStarted or some page elements will be misaligned. + procedure WMMove(var aMessage : TWMMove); message WM_MOVE; + procedure WMMoving(var aMessage : TMessage); message WM_MOVING; + // CEF: You also have to handle these two messages to set GlobalCEFApp.OsmodalLoop + procedure WMEnterMenuLoop(var aMessage: TMessage); message WM_ENTERMENULOOP; + procedure WMExitMenuLoop(var aMessage: TMessage); message WM_EXITMENULOOP; + + procedure CreateBrowser; + procedure Navigate; overload; + procedure DoBeforeBrowse(const url: string; isMain, isPopup, ShouldOpenUrlIfNotHandled: Boolean; out Handled: Boolean); + public + procedure SetFocus; override; + procedure StartClose; + procedure Navigate(const url: string); overload; + function HasFocus: Boolean; + property ShouldShowContextMenu: Boolean read FShouldShowContextMenu write FShouldShowContextMenu; + property ShouldOpenRemoteUrlsInBrowser: Boolean read FShouldOpenRemoteUrlsInBrowser write FShouldOpenRemoteUrlsInBrowser; + property OnAfterCreated: TNotifyEvent read FOnAfterCreated write FOnAfterCreated; + property OnBeforeBrowseExSync: TCEFHostBeforeBrowseExSyncEvent read FOnBeforeBrowseExSync write FOnBeforeBrowseExSync; + property OnBeforeBrowseSync: TCEFHostBeforeBrowseSyncEvent read FOnBeforeBrowseSync write FOnBeforeBrowseSync; + property OnCommand: TCEFCommandEvent read FOnCommand write FOnCommand; + property OnBeforeBrowseEx: TCEFHostBeforeBrowseExEvent read FOnBeforeBrowseEx write FOnBeforeBrowseEx; + property OnBeforeBrowse: TCEFHostBeforeBrowseEvent read FOnBeforeBrowse write FOnBeforeBrowse; + property OnHelpTopic: TNotifyEvent read FOnHelpTopic write FOnHelpTopic; + property OnLoadEnd: TNotifyEvent read FOnLoadEnd write FOnLoadEnd; + property OnTitleChange: TCEFHostTitleChangeEvent read FOnTitleChange write FOnTitleChange; + property OnPreKeySyncEvent: TCEFHostPreKeySyncEvent read FOnPreKeySyncEvent write FOnPreKeySyncEvent; + property OnKeyEvent: TCEFHostKeyEvent read FOnKeyEvent write FOnKeyEvent; + property OnLoadingStateChange: TCEFHostLoadingStateChangeEvent read FOnLoadingStateChange write FOnLoadingStateChange; + end; + +// Helpers to make sure we don't accidentally code +// VCL references into non-VCL-thread functions +procedure AssertVclThread; +procedure AssertCefThread; + +implementation + +uses + System.StrUtils, + Winapi.ShellApi, + + ErrorControlledRegistry, + utilhttp, + uCEFApplication, + uCEFConstants, + uCEFProcessMessage, + VersionInfo; + +{$R *.DFM} + +{ TfrmCEFHost } + +// Destruction steps +// ================= +// 1. The FormCloseQuery event sets CanClose to False and calls TChromiumWindow.CloseBrowser, which triggers the TChromiumWindow.OnClose event. +// 2. The TChromiumWindow.OnClose event calls TChromiumWindow.DestroyChildWindow which triggers the TChromiumWindow.OnBeforeClose event. +// 3. TChromiumWindow.OnBeforeClose sets FCanClose to True and closes the form. + +procedure AssertVclThread; +begin + Assert(GetCurrentThreadId = MainThreadID); +end; + +procedure AssertCefThread; +begin + Assert(GetCurrentThreadId <> MainThreadID); +end; + +procedure TframeCEFHost.StartClose; +begin + AssertVclThread; + Visible := False; + FIsClosing := True; + cef.CloseBrowser(True); +end; + +procedure TframeCEFHost.StartShutdown(CompletionHandler: TShutdownCompletionHandlerEvent); +begin + AssertVclThread; + OutputDebugString(PChar('TframeCEFHost.StartShutdown')); + FIsClosing := True; + FShutdownCompletionHandler := CompletionHandler; + + // If the browser has not been initialized, we'll not get the close signal, so we + // post it to occur on next idle. + if cef.Initialized + then cef.CloseBrowser(False) + else PostMessage(FCallbackWnd, CEF_AFTERDESTROY, 0, 0); +end; + +procedure TframeCEFHost.FormCreate(Sender: TObject); +begin + AssertVclThread; + inherited; + FApplicationHandle := Application.Handle; // take a copy to avoid Vcl thread mismatches in cef callbacks + + // We need our own window handle for events, because VCL windows can be destroyed + // and recreated at any time. With our own handle, we can guarantee the lifetime + // of it across threads. + FCallbackWnd := AllocateHWnd(CallbackWndProc); + FInitializeCEF.RegisterWindow(Self); +// CreateBrowser; +end; + +procedure TframeCEFHost.FormDestroy(Sender: TObject); +begin + AssertVclThread; +// OutputDebugString(PChar('TframeCEFHost.FormDestroy')); + inherited; + FInitializeCEF.UnregisterWindow(Self); + DeallocateHWnd(FCallbackWnd); +end; + +procedure TframeCEFHost.FormShow(Sender: TObject); +begin + AssertVclThread; + inherited; + PostMessage(FCallbackWnd, CEF_SHOW, 0, 0); +end; + +function TframeCEFHost.GetDebugInfo: string; +begin + Result := FNextURL; + if Assigned(Owner) then Result := Owner.ClassName+':'+Result; +end; + +function TframeCEFHost.HasFocus: Boolean; +begin + AssertVclThread; + Result := Assigned(cefwp) and cefwp.HandleAllocated and IsChild(cefwp.Handle, GetFocus); +end; + +procedure TframeCEFHost.Handle_CEF_SHOW(var message: TMessage); +begin + AssertVclThread; + CreateBrowser; +end; + +procedure TframeCEFHost.Handle_CEF_TITLECHANGE(var message: TMessage); +var + p: PCEFTitleChangeEventData; +begin + AssertVclThread; + p := PCEFTitleChangeEventData(message.LParam); + + if Assigned(FOnTitleChange) then + FOnTitleChange(Self, p.title); + + FreeMem(p); +end; + +procedure TframeCEFHost.cefWidgetCompMsg(Sender: TObject; var aMessage: TMessage; + var aHandled: Boolean); +begin + AssertCefThread; + if aMessage.Msg = WM_SETFOCUS then + PostMessage(FCallbackWnd, CEF_SETFOCUS, 0, 0); +end; + +procedure TframeCEFHost.CreateBrowser; +begin + AssertVclThread; + FIsCreated := True; + tmrCreateBrowser.Enabled := not cef.CreateBrowser(cefwp); +end; + +procedure TframeCEFHost.Navigate(const url: string); +begin + AssertVclThread; + FNextURL := url; + Navigate; +end; + +procedure TframeCEFHost.Navigate; +begin + AssertVclThread; + if FNextURL = '' then + Exit; + + if not FIsCreated then + begin + cef.DefaultUrl := FNextURL; + FNextURL := ''; + Exit; + end; + + if not cef.Initialized then + begin + // After initialization, refresh will happen + // See cefAfterCreated + Exit; + end; + + cef.LoadURL(FNextURL); +end; + +procedure TframeCEFHost.SetFocus; +begin + AssertVclThread; + if not FIsClosing and Assigned(cefwp) and Assigned(cef) and cefwp.CanFocus then + begin + GetParentForm(Self).ActiveControl := Self; + cef.SetFocus(True); + end; +end; + +procedure TframeCEFHost.CallbackWndProc(var Message: TMessage); +begin + AssertVclThread; + case Message.Msg of + CEF_DESTROY: Handle_CEF_DESTROY(Message); + CEF_AFTERDESTROY: Handle_CEF_AFTERDESTROY(Message); + CEF_AFTERCREATE: Handle_CEF_AFTERCREATE(Message); + CEF_SHOW: Handle_CEF_SHOW(Message); + CEF_LOADEND: Handle_CEF_LOADEND(Message); + CEF_KEYEVENT: Handle_CEF_KEYEVENT(Message); + CEF_BEFOREBROWSE: Handle_CEF_BEFOREBROWSE(Message); + CEF_TITLECHANGE: Handle_CEF_TITLECHANGE(Message); + CEF_COMMAND: Handle_CEF_COMMAND(Message); + CEF_SETFOCUS: Handle_CEF_SETFOCUS(Message); + CEF_LOADINGSTATECHANGE: Handle_CEF_LOADINGSTATECHANGE(Message); + end; + + if Self <> nil then + Message.Result := DefWindowProc(FCallbackWnd, Message.Msg, Message.WParam, Message.LParam); +end; + +procedure TframeCEFHost.Handle_CEF_AFTERCREATE(var Message: TMessage); +begin + AssertVclThread; + Navigate; + if Assigned(FOnAfterCreated) then + FOnAfterCreated(Self); +end; + +procedure TframeCEFHost.cefAfterCreated(Sender: TObject; + const browser: ICefBrowser); +begin + AssertCefThread; + PostMessage(FCallbackWnd, CEF_AFTERCREATE, 0, 0); +end; + +procedure TframeCEFHost.Handle_CEF_AFTERDESTROY(var Message: TMessage); +begin + AssertVclThread; + if Assigned(FShutdownCompletionHandler) then + begin + FShutdownCompletionHandler(Self); + FShutdownCompletionHandler := nil; + end; +end; + +function IsLocalURL(URL: string): Boolean; +begin + Result := + URL.StartsWith('file:') or + URL.StartsWith('/') or + URL.StartsWith('http://localhost:') or + URL.StartsWith('http://localhost/') or + URL.StartsWith('http://127.0.0.1:') or + URL.StartsWith('http://127.0.0.1/'); +end; + +function IsPDFURL(URL: string): Boolean; +begin + Result := LowerCase(URL).EndsWith('.pdf'); +end; + +procedure TframeCEFHost.Handle_CEF_BEFOREBROWSE(var message: TMessage); +var + params: TStringList; + url: string; + isMain, isPopup, wasHandled, + shouldOpenUrlIfNotHandled: Boolean; +begin + AssertVclThread; + + params := TStringList(message.LParam); + url := params[0]; + wasHandled := (message.WParam and 1) = 1; + shouldOpenUrlIfNotHandled := (message.WParam and 2) = 2; + isPopup := (message.WParam and 4) = 4; + isMain := (message.WParam and 8) = 8; + + if wasHandled then + begin + if Assigned(FOnBeforeBrowseEx) then + begin + FOnBeforeBrowseEx(Self, url, isMain, isPopup, wasHandled); + end + else if Assigned(FOnBeforeBrowse) then + begin + FOnBeforeBrowse(Self, url, isPopup, wasHandled); + end; + + if FShouldOpenRemoteUrlsInBrowser and (not IsLocalURL(URL) or IsPDFURL(URL)) then + begin + {$MESSAGE HINT 'Refactor how remote URLs are handled'} + // TODO: refactor links + TUtilExecute.URL(url); + end; + end + else if shouldOpenUrlIfNotHandled then + cef.LoadURL(url); + + params.Free; +end; + +procedure TframeCEFHost.Handle_CEF_COMMAND(var message: TMessage); +var + params: TStringList; + command: string; +begin + AssertVclThread; + params := TStringList(message.LParam); + + if Assigned(FOnCommand) then + begin + params.Delete(0); // url; not really used currently + command := params[0]; + params.Delete(0); + FOnCommand(Self, command, params); + end; + + params.Free; +end; + +procedure TframeCEFHost.cefBeforeBrowse(Sender: TObject; + const browser: ICefBrowser; const frame: ICefFrame; + const request: ICefRequest; user_gesture, isRedirect: Boolean; + out Result: Boolean); +begin + AssertCefThread; + + DoBeforeBrowse(request.Url, frame.IsMain, False, False, Result); +end; + +procedure TframeCEFHost.DoBeforeBrowse(const url: string; isMain, isPopup, ShouldOpenUrlIfNotHandled: Boolean; out Handled: Boolean); +var + params: TStringList; + wParam: DWORD; +begin + AssertCefThread; + + Handled := False; + + if Assigned(FOnBeforeBrowseExSync) then + begin + FOnBeforeBrowseExSync(Self, url, isMain, isPopup, Handled); + end + else if Assigned(FOnBeforeBrowseSync) then + begin + FOnBeforeBrowseSync(Self, url, isPopup, Handled); + end; + + if not Handled and GetParamsFromURL(Url, params) then + begin + Handled := True; + // Use OnCommand for keyman: URLs + params.Insert(0, Url); + PostMessage(FCallbackWnd, CEF_COMMAND, 0, LPARAM(params)); + end + else + begin + // Use OnBeforeBrowse for other URLs + if not Handled and FShouldOpenRemoteUrlsInBrowser and (not IsLocalURL(URL) or IsPDFURL(URL)) then + Handled := True; + + params := TStringList.Create; + params.Add(Url); + wParam := 0; + if Handled then + wParam := wParam or 1; + if ShouldOpenUrlIfNotHandled then + wParam := wParam or 2; + if isPopup then + wParam := wParam or 4; + if isMain then + wParam := wParam or 8; + PostMessage(FCallbackWnd, CEF_BEFOREBROWSE, wParam, LPARAM(params)); + end; +end; + +procedure TframeCEFHost.cefBeforeClose(Sender: TObject; const browser: ICefBrowser); +begin + AssertCefThread; + PostMessage(FCallbackWnd, CEF_AFTERDESTROY, 0, 0); +end; + +procedure TframeCEFHost.cefClose(Sender: TObject; const browser: ICefBrowser; + var aAction: TCefCloseBrowserAction); +begin + AssertCefThread; + PostMessage(FCallbackWnd, CEF_DESTROY, 0, 0); + aAction := cbaClose; +end; + +procedure TframeCEFHost.Handle_CEF_DESTROY(var Message: TMessage); +begin + AssertVclThread; + if Assigned(cefwp) then + cefwp.DestroyChildWindow; + FreeAndNil(cefwp); +end; + +procedure TframeCEFHost.tmrCreateBrowserTimer(Sender: TObject); +begin + AssertVclThread; + tmrCreateBrowser.Enabled := False; + CreateBrowser; +end; + +procedure TframeCEFHost.cefLoadEnd(Sender: TObject; const browser: ICefBrowser; + const frame: ICefFrame; httpStatusCode: Integer); +begin + AssertCefThread; + PostMessage(FCallbackWnd, CEF_LOADEND, WPARAM(httpStatusCode), 0); +end; + +procedure TframeCEFHost.cefLoadingStateChange(Sender: TObject; + const browser: ICefBrowser; isLoading, canGoBack, canGoForward: Boolean); +var + v: Integer; +begin + AssertCefThread; + v := 0; + if isLoading then v := v or CEF_LOADINGSTATECHANGE_ISLOADING; + if canGoBack then v := v or CEF_LOADINGSTATECHANGE_CANGOBACK; + if canGoForward then v := v or CEF_LOADINGSTATECHANGE_CANGOFORWARD; + PostMessage(FCallbackWnd, CEF_LOADINGSTATECHANGE, v, 0); +end; + +procedure TframeCEFHost.Handle_CEF_KEYEVENT(var message: TMessage); +var + p: PCEFHostKeyEventData; + wasHandled: Boolean; + wasShortcut: Boolean; +begin + p := PCEFHostKeyEventData(message.LParam); + + if p.event.windows_key_code = VK_F1 then + begin + if Assigned(FOnHelpTopic) then FOnHelpTopic(Self); // TODO: frmKeymanDeveloper.HelpTopic(Self) + end + else if p.event.windows_key_code = VK_F12 then + begin + cef.ShowDevTools(Point(Low(Integer),Low(Integer)), nil); + end + else if Assigned(FOnKeyEvent) then + begin + wasHandled := message.WParamLo <> 0; + wasShortcut := message.WParamHi <> 0; + FOnKeyEvent(Self, p^, wasShortcut, wasHandled); + end; + FreeMem(p); +end; + +procedure TframeCEFHost.Handle_CEF_LOADEND(var message: TMessage); +begin + if csDestroying in ComponentState then + Exit; + + // The focus needs to be set again for key events to + // be passed to the CEF window, even if it appears + // to already be focused to the expected window. + if IsChild(Handle, GetFocus) then + SetFocus; + + if Assigned(FOnLoadEnd) then + FOnLoadEnd(Self); +end; + +procedure TframeCEFHost.Handle_CEF_LOADINGSTATECHANGE(var message: TMessage); +begin + if csDestroying in ComponentState then + Exit; + AssertVclThread; + + if Assigned(FOnLoadingStateChange) then + FOnLoadingStateChange(Self, + (message.WParam and CEF_LOADINGSTATECHANGE_ISLOADING) <> 0, + (message.WParam and CEF_LOADINGSTATECHANGE_CANGOBACK) <> 0, + (message.WParam and CEF_LOADINGSTATECHANGE_CANGOFORWARD) <> 0); +end; + +procedure TframeCEFHost.Handle_CEF_SETFOCUS(var message: TMessage); +begin + AssertVclThread; + if Assigned(cefwp) and cefwp.Visible and cefwp.CanFocus then + GetParentForm(cefwp).ActiveControl := cefwp; +end; + +procedure TframeCEFHost.cefPreKeyEvent(Sender: TObject; + const browser: ICefBrowser; const event: PCefKeyEvent; osEvent: TCefEventHandle; + out isKeyboardShortcut, Result: Boolean); +var + p: PCEFHostKeyEventData; +begin + AssertCefThread; + Result := False; + + p := AllocMem(Sizeof(TCEFHostKeyEventData)); + p.browserid := browser.Identifier; + p.event := event^; + if Assigned(osEvent) then + p.osEvent := osEvent^; + + if event.kind in [TCefKeyEventType.KEYEVENT_KEYDOWN, TCefKeyEventType.KEYEVENT_RAWKEYDOWN] then + begin + if Assigned(FOnPreKeySyncEvent) then + begin + FOnPreKeySyncEvent(Self, p^, isKeyboardShortcut, Result); + end; + + if not Result then // only run this if the prekeysyncevent didn't swallow the keystroke + begin + if event.windows_key_code = VK_F1 then + begin + isKeyboardShortcut := True; + Result := True; + end + else if event.windows_key_code = VK_F12 then + begin + isKeyboardShortcut := True; + Result := True; + end + else if event.windows_key_code <> VK_CONTROL then + begin + if SendMessage(FApplicationHandle, CM_APPKEYDOWN, event.windows_key_code, 0) = 1 then + begin + isKeyboardShortcut := True; + Result := True; + end; + end; + end; + + PostMessage(FCallbackWnd, CEF_KEYEVENT, MAKELONG(WORD(Result), WORD(isKeyboardShortcut)), LPARAM(p)); + end; +end; + + +procedure TframeCEFHost.cefBeforePopup(Sender: TObject; + const browser: ICefBrowser; const frame: ICefFrame; const targetUrl, + targetFrameName: ustring; targetDisposition: TCefWindowOpenDisposition; + userGesture: Boolean; const popupFeatures: TCefPopupFeatures; + var windowInfo: TCefWindowInfo; var client: ICefClient; + var settings: TCefBrowserSettings; + var extra_info: ICefDictionaryValue; + var noJavascriptAccess, Result: Boolean); +begin + AssertCefThread; + DoBeforeBrowse(targetUrl, frame.IsMain, True, True, Result); +end; + +procedure TframeCEFHost.cefRunContextMenu(Sender: TObject; + const browser: ICefBrowser; const frame: ICefFrame; + const params: ICefContextMenuParams; const model: ICefMenuModel; + const callback: ICefRunContextMenuCallback; var aResult: Boolean); +begin + AssertCefThread; + // Return FALSE to show default context menu + aResult := not FShouldShowContextMenu and (GetKeyState(VK_SHIFT) >= 0); +end; + +procedure TframeCEFHost.cefSetFocus(Sender: TObject; const browser: ICefBrowser; + source: TCefFocusSource; out Result: Boolean); +begin + Result := source = FOCUS_SOURCE_NAVIGATION; +end; + +procedure TframeCEFHost.cefTitleChange(Sender: TObject; + const browser: ICefBrowser; const title: ustring); +var + p: PCEFTitleChangeEventData; +begin + AssertCefThread; + + p := AllocMem(SizeOf(TCEFTitleChangeEventData)); + p.browserid := browser.Identifier; + p.title := title; + + PostMessage(FCallbackWnd, CEF_TITLECHANGE, 0, LPARAM(p)); +end; + +procedure TframeCEFHost.WMEnterMenuLoop(var aMessage: TMessage); +begin + AssertVclThread; + inherited; + if (aMessage.wParam = 0) and (GlobalCEFApp <> nil) then GlobalCEFApp.OsmodalLoop := True; +end; + +procedure TframeCEFHost.WMExitMenuLoop(var aMessage: TMessage); +begin + AssertVclThread; + inherited; + if (aMessage.wParam = 0) and (GlobalCEFApp <> nil) then GlobalCEFApp.OsmodalLoop := False; +end; + +procedure TframeCEFHost.WMMove(var aMessage: TWMMove); +begin + AssertVclThread; + inherited; + if cef <> nil then cef.NotifyMoveOrResizeStarted; +end; + +procedure TframeCEFHost.WMMoving(var aMessage: TMessage); +begin + AssertVclThread; + inherited; + if cef <> nil then cef.NotifyMoveOrResizeStarted; +end; + +end. From 1c175f00805556ca05385732eed28ec019d290d0 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 13 Apr 2026 09:15:16 +0200 Subject: [PATCH 038/140] chore(android): uncomment main_text_color --- android/KMAPro/kMAPro/src/main/res/values/colors.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/KMAPro/kMAPro/src/main/res/values/colors.xml b/android/KMAPro/kMAPro/src/main/res/values/colors.xml index 4f59937b80..ab162552d7 100644 --- a/android/KMAPro/kMAPro/src/main/res/values/colors.xml +++ b/android/KMAPro/kMAPro/src/main/res/values/colors.xml @@ -35,6 +35,6 @@ #FFFFFF @color/keyman_blue - + #000000 From fe8861dd1525df45f0e800365737394187c210b1 Mon Sep 17 00:00:00 2001 From: Mengchou97 Date: Mon, 20 Apr 2026 10:26:52 +0700 Subject: [PATCH 039/140] fixed the pull request problem --- android/KMAPro/kMAPro/build.gradle | 4 +- .../kMAPro/src/main/AndroidManifest.xml | 8 - .../com/tavultesoft/kmapro/MainActivity.java | 147 ++++++++++--- .../src/main/res/layout/activity_main.xml | 12 +- .../layout/check_chrome_webview_layout.xml | 11 +- .../kMAPro/src/main/res/menu/nav_menu.xml | 200 ++++++++---------- .../kMAPro/src/main/res/values/dimens.xml | 2 + .../kMAPro/src/main/res/values/strings.xml | 35 ++- .../kMAPro/src/main/res/values/styles.xml | 2 +- 9 files changed, 256 insertions(+), 165 deletions(-) diff --git a/android/KMAPro/kMAPro/build.gradle b/android/KMAPro/kMAPro/build.gradle index df8576d044..77ab2fa736 100644 --- a/android/KMAPro/kMAPro/build.gradle +++ b/android/KMAPro/kMAPro/build.gradle @@ -32,7 +32,7 @@ android { defaultConfig { applicationId "com.tavultesoft.kmapro" - minSdkVersion 23 + minSdkVersion 21 targetSdkVersion 35 //println "===DUMPING PROPERTIES===" @@ -184,8 +184,6 @@ dependencies { implementation ('com.github.kenglxn.QRGen:android:3.0.1') { transitive = true } - implementation 'com.google.android.gms:play-services-maps3d:0.2.0' - //implementation 'com.google.android.gms:play-services-maps:19.0.0' } /*def void dumpProperties(it){ diff --git a/android/KMAPro/kMAPro/src/main/AndroidManifest.xml b/android/KMAPro/kMAPro/src/main/AndroidManifest.xml index 957125fb87..4964e7b450 100644 --- a/android/KMAPro/kMAPro/src/main/AndroidManifest.xml +++ b/android/KMAPro/kMAPro/src/main/AndroidManifest.xml @@ -17,7 +17,6 @@ https://support.google.com/googleplay/android-developer/answer/9214102# --> - - - - - - - diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index 5d1f0fe8dd..cda48deae4 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -136,6 +136,7 @@ import android.view.MenuItem; import androidx.appcompat.widget.AppCompatCheckBox; import io.sentry.android.core.SentryAndroid; +import io.sentry.Sentry; public class MainActivity extends BaseActivity implements OnKeyboardEventListener, OnKeyboardDownloadEventListener, ActivityCompat.OnRequestPermissionsResultCallback { @@ -295,7 +296,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene drawerToggle.syncState(); navigationView = findViewById(R.id.nav_view); - navigationView.setItemMaxLines(2); + navigationView.setItemMaxLines(4); navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() { @Override public boolean onNavigationItemSelected(@NonNull MenuItem item) { @@ -388,7 +389,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } else if (id == R.id.nav_palette) { drawerLayout.closeDrawers(); openThemeDialog(); - } else if (id == R.id.nav_about_current_keyboard) { + } else if (id == R.id.nav_about_current_keyboard || id == R.id.nav_help_active_keyboard) { drawerLayout.closeDrawers(); showCurrentKeyboardSettings(); } @@ -396,6 +397,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } }); updateCurrentKeyboardDrawerItemTitle(navigationView); + updateInstalledLanguagesDrawerTitle(navigationView); initializeDrawerItemSubtitles(navigationView); initializeDrawerToggleOptions(navigationView); initializeDrawerCheckboxOptions(navigationView); @@ -413,6 +415,8 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene public void onDrawerOpened(View drawerView) { if (navigationView != null) { updateCurrentKeyboardDrawerItemTitle(navigationView); + updateInstalledLanguagesDrawerTitle(navigationView); + initializeDrawerItemSubtitles(navigationView); } } @@ -444,7 +448,11 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } private void openThemeDialog() { - String[] options = { "Dark Mode", "Light Mode", "System Default" }; + String[] options = { + getString(R.string.theme_dark_mode), + getString(R.string.theme_light_mode), + getString(R.string.theme_system_default) + }; SharedPreferences themePrefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE); int currentMode = themePrefs.getInt(KEY_THEME_MODE, AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM); @@ -458,7 +466,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene checkedItem = 2; AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle("Choose Theme"); + builder.setTitle(getString(R.string.theme_choose)); builder.setSingleChoiceItems(options, checkedItem, (dialog, which) -> { int modeToApply; @@ -479,10 +487,13 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene // Apply immediately AppCompatDelegate.setDefaultNightMode(modeToApply); getDelegate().applyDayNight(); // ensures current activity updates + if (navigationView != null) { + updateThemeDrawerSubtitle(navigationView); + } dialog.dismiss(); }); - builder.setNegativeButton("Cancel", (dialog, which) -> dialog.dismiss()); + builder.setNegativeButton(android.R.string.cancel, (dialog, which) -> dialog.dismiss()); builder.show(); } @@ -539,6 +550,9 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene if (navigationView != null) { refreshDrawerSystemKeyboardCheckboxes(navigationView); + updateCurrentKeyboardDrawerItemTitle(navigationView); + updateInstalledLanguagesDrawerTitle(navigationView); + initializeDrawerItemSubtitles(navigationView); } if (textView != null) { @@ -867,6 +881,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene keyboardToolbarContainer = findViewById(R.id.keyboardToolbarContainer); if (keyboardToolbarToggleButton == null || keyboardToolbarContainer == null) { + reportKeyboardToolbarLayoutIssue("Keyboard toolbar layout is missing required views."); return; } @@ -888,6 +903,8 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene showShareDialog(); setKeyboardToolbarExpanded(false, true); }); + } else { + reportKeyboardToolbarLayoutIssue("Keyboard toolbar share button is missing."); } View textSizeButton = findViewById(R.id.keyboardToolbarTextSizeButton); @@ -896,6 +913,8 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene showTextSizeDialog(); setKeyboardToolbarExpanded(false, true); }); + } else { + reportKeyboardToolbarLayoutIssue("Keyboard toolbar text size button is missing."); } View clearButton = findViewById(R.id.keyboardToolbarClearButton); @@ -904,6 +923,15 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene showClearTextDialog(); setKeyboardToolbarExpanded(false, true); }); + } else { + reportKeyboardToolbarLayoutIssue("Keyboard toolbar clear button is missing."); + } + } + + private void reportKeyboardToolbarLayoutIssue(String message) { + KMLog.LogError(TAG, message); + if (Sentry.isEnabled()) { + Sentry.captureMessage(message); } } @@ -915,7 +943,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene isKeyboardToolbarExpanded = expanded; keyboardToolbarContainer.setVisibility(expanded ? View.VISIBLE : View.GONE); - float targetRotation = expanded ? 180f : 0f; + float targetRotation = expanded ? 0f : 180f; if (animateRotation) { keyboardToolbarToggleButton.animate().rotation(targetRotation).setDuration(180).start(); } else { @@ -924,9 +952,11 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } private void updateKeyboardToolbarPosition() { - int keyboardTopOffset = KMManager.getKeyboardHeight(this) + KMManager.getBannerHeight(this) + dpToPx(12); + int keyboardTopOffset = KMManager.getKeyboardHeight(this) + KMManager.getBannerHeight(this) + + getResources().getDimensionPixelSize(R.dimen.keyboard_toolbar_keyboard_gap); updateBottomMargin(keyboardToolbarToggleButton, keyboardTopOffset); - updateBottomMargin(keyboardToolbarContainer, dpToPx(8)); + updateBottomMargin(keyboardToolbarContainer, + getResources().getDimensionPixelSize(R.dimen.keyboard_toolbar_container_bottom_margin)); } private void updateBottomMargin(View view, int bottomMargin) { @@ -946,11 +976,6 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } } - private int dpToPx(int dp) { - float density = getResources().getDisplayMetrics().density; - return Math.round(dp * density); - } - public void downloadKMP(String packageId, String bcp47, KmpInstallMode installMode) { Uri downloadUri = bcp47 == null ? Uri.parse(KMString.format("https://keyman.com/go/package/download/%s", new Object[] { packageId })) @@ -1467,17 +1492,17 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } private void initializeDrawerItemSubtitles(NavigationView navigationView) { - setDrawerItemSubtitle(navigationView, R.id.nav_spacebar_caption, getString(R.string.drawer_subtitle_spacebar_caption)); setDrawerItemSubtitle(navigationView, R.id.nav_toggle_show_osk, getString(R.string.drawer_subtitle_show_osk)); setDrawerItemSubtitle(navigationView, R.id.nav_toggle_send_crash_report, getString(R.string.drawer_subtitle_send_crash_report)); - setDrawerItemSubtitle(navigationView, R.id.nav_help, - getString(R.string.drawer_subtitle_about)); setDrawerItemSubtitle(navigationView, R.id.nav_about_current_keyboard, - getString(R.string.drawer_subtitle_about_current_keyboard)); + getString(R.string.drawer_subtitle_about_current_keyboard)); + updateThemeDrawerSubtitle(navigationView); + updateCurrentKeyboardDrawerSubtitle(navigationView); + updateActiveKeyboardHelpDrawerItem(navigationView); } private void updateCurrentKeyboardDrawerItemTitle(NavigationView navigationView) { @@ -1486,15 +1511,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene return; } - Keyboard currentKeyboard = getBestAvailableKeyboard(); - String newTitle; - if (currentKeyboard != null && currentKeyboard.getKeyboardName() != null - && !currentKeyboard.getKeyboardName().isEmpty()) { - newTitle = getString(R.string.drawer_about_current_keyboard) + ": " + currentKeyboard.getKeyboardName(); - } else { - newTitle = getString(R.string.drawer_about_current_keyboard) + ": " - + getString(R.string.drawer_about_no_active_keyboard); - } + String newTitle = getString(R.string.drawer_about_current_keyboard); if (!newTitle.equals(lastCurrentKeyboardDrawerTitle)) { menuItem.setTitle(newTitle); @@ -1502,6 +1519,84 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } } + private void updateCurrentKeyboardDrawerSubtitle(NavigationView navigationView) { + setDrawerItemSubtitle(navigationView, R.id.nav_about_current_keyboard, getCurrentKeyboardDisplayName()); + } + + private void updateThemeDrawerSubtitle(NavigationView navigationView) { + setDrawerItemSubtitle( + navigationView, + R.id.nav_palette, + getString(R.string.drawer_subtitle_theme, getThemeModeLabel()) + ); + } + + private void updateActiveKeyboardHelpDrawerItem(NavigationView navigationView) { + MenuItem menuItem = navigationView.getMenu().findItem(R.id.nav_help_active_keyboard); + if (menuItem == null) { + return; + } + + String keyboardName = getCurrentKeyboardDisplayName(); + if (keyboardName.equals(getString(R.string.drawer_about_no_active_keyboard))) { + menuItem.setTitle(getString(R.string.drawer_title_active_keyboard_help_fallback)); + setDrawerItemSubtitle(navigationView, R.id.nav_help_active_keyboard, keyboardName); + return; + } + + menuItem.setTitle(getString(R.string.drawer_title_active_keyboard_help, keyboardName)); + setDrawerItemSubtitle( + navigationView, + R.id.nav_help_active_keyboard, + getString(R.string.drawer_subtitle_active_keyboard_help, keyboardName) + ); + } + + private void updateInstalledLanguagesDrawerTitle(NavigationView navigationView) { + MenuItem menuItem = navigationView.getMenu().findItem(R.id.nav_installed_languages); + if (menuItem == null) { + return; + } + + menuItem.setTitle(getInstalledLanguagesText()); + } + + private String getInstalledLanguagesText() { + Dataset installedDataset = KMManager.getInstalledDataset(context); + if (installedDataset == null) { + return getResources().getQuantityString(R.plurals.installed_languages, 0, 0); + } + + int langCount = 0; + for (Dataset.LanguageDataset language : installedDataset.asList()) { + if (language.keyboards.size() > 0) { + langCount++; + } + } + return getResources().getQuantityString(R.plurals.installed_languages, langCount, langCount); + } + + private String getCurrentKeyboardDisplayName() { + Keyboard currentKeyboard = getBestAvailableKeyboard(); + if (currentKeyboard != null && currentKeyboard.getKeyboardName() != null + && !currentKeyboard.getKeyboardName().isEmpty()) { + return currentKeyboard.getKeyboardName(); + } + return getString(R.string.drawer_about_no_active_keyboard); + } + + private String getThemeModeLabel() { + SharedPreferences themePrefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE); + int currentMode = themePrefs.getInt(KEY_THEME_MODE, AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM); + if (currentMode == AppCompatDelegate.MODE_NIGHT_YES) { + return getString(R.string.theme_dark_mode); + } + if (currentMode == AppCompatDelegate.MODE_NIGHT_NO) { + return getString(R.string.theme_light_mode); + } + return getString(R.string.theme_system_default); + } + private void showCurrentKeyboardSettings() { Keyboard currentKeyboard = getBestAvailableKeyboard(); if (currentKeyboard == null) { diff --git a/android/KMAPro/kMAPro/src/main/res/layout/activity_main.xml b/android/KMAPro/kMAPro/src/main/res/layout/activity_main.xml index c7a3e46821..7c3e004b0d 100644 --- a/android/KMAPro/kMAPro/src/main/res/layout/activity_main.xml +++ b/android/KMAPro/kMAPro/src/main/res/layout/activity_main.xml @@ -24,8 +24,8 @@ @@ -51,14 +51,15 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" + android:gravity="center_horizontal" android:visibility="gone" android:padding="6dp" android:background="@drawable/bg_keyboard_toolbar_container" android:elevation="6dp" android:outlineProvider="background" app:layout_constraintBottom_toTopOf="@id/keyboardToolbarToggleButton" - app:layout_constraintEnd_toEndOf="parent" - android:layout_marginEnd="12dp" + app:layout_constraintStart_toStartOf="@id/keyboardToolbarToggleButton" + app:layout_constraintEnd_toEndOf="@id/keyboardToolbarToggleButton" android:layout_marginBottom="8dp"> diff --git a/android/KMAPro/kMAPro/src/main/res/layout/check_chrome_webview_layout.xml b/android/KMAPro/kMAPro/src/main/res/layout/check_chrome_webview_layout.xml index ea5a9220d5..396f02ac41 100644 --- a/android/KMAPro/kMAPro/src/main/res/layout/check_chrome_webview_layout.xml +++ b/android/KMAPro/kMAPro/src/main/res/layout/check_chrome_webview_layout.xml @@ -3,20 +3,23 @@ android:id="@+id/checkWebViewChromeLayout" android:layout_width="match_parent" android:layout_height="wrap_content" + android:paddingVertical="4dp" android:background="@android:color/white" + android:gravity="center_vertical" android:orientation="horizontal"> \ No newline at end of file + + + + + + + + + + + + diff --git a/android/KMAPro/kMAPro/src/main/res/values/dimens.xml b/android/KMAPro/kMAPro/src/main/res/values/dimens.xml index f2e6ac5b3f..f723410198 100644 --- a/android/KMAPro/kMAPro/src/main/res/values/dimens.xml +++ b/android/KMAPro/kMAPro/src/main/res/values/dimens.xml @@ -25,4 +25,6 @@ 16dp 0dp 24sp + 12dp + 8dp diff --git a/android/KMAPro/kMAPro/src/main/res/values/strings.xml b/android/KMAPro/kMAPro/src/main/res/values/strings.xml index 83c966d100..fe576a08d1 100644 --- a/android/KMAPro/kMAPro/src/main/res/values/strings.xml +++ b/android/KMAPro/kMAPro/src/main/res/values/strings.xml @@ -18,12 +18,16 @@ Show keyboard name on spacebar When physical keyboard connected - When on, crash reports will be sent + Allow error reports to be sent to Keyman team - Learn how to use the keyboard - Help and QR code for the active keyboard - Current keyboard + Learn how to use Keyman + No active keyboard + About active keyboard No active keyboard + Current setting: %1$s + Learn how to use the %1$s keyboard + %1$s help + Active keyboard help Text Size @@ -118,12 +122,12 @@ - Installed languages + Installed languages (%1$d) Installed languages (%1$d) - Install Keyboard or Dictionary + Install keyboard or dictionary Display language @@ -163,7 +167,7 @@ Do not show caption on spacebar - Show On-Screen Keyboard + Show on-screen keyboard When physical keyboard is connected @@ -181,13 +185,24 @@ When off, only shown when predictive text is enabled - Allow sending crash reports over network + Automatic error reporting - When on, crash reports will be sent + Allow error reports to be sent to Keyman team - When off, crash reports won\'t be sent + Error reports are not sent to Keyman team + + + User guide + Update keyboards + Installed languages + Show "Get Started" on startup + Theme + Choose theme + Dark mode + Light mode + System default Install from keyman.com diff --git a/android/KMAPro/kMAPro/src/main/res/values/styles.xml b/android/KMAPro/kMAPro/src/main/res/values/styles.xml index b0f9f770e1..6b1b7fa36e 100644 --- a/android/KMAPro/kMAPro/src/main/res/values/styles.xml +++ b/android/KMAPro/kMAPro/src/main/res/values/styles.xml @@ -71,7 +71,7 @@ @color/menuBgColor From d3a76b8c370ec6b8a4f3779521f0a3021a01d246 Mon Sep 17 00:00:00 2001 From: Mengchou97 Date: Mon, 20 Apr 2026 12:45:42 +0700 Subject: [PATCH 040/140] replace icon --- .../main/res/drawable/ic_keyboard_default.xml | 53 +++++++ .../res/drawable/ic_keyboard_physical.xml | 132 ++++++------------ .../kMAPro/src/main/res/menu/nav_menu.xml | 2 +- 3 files changed, 94 insertions(+), 93 deletions(-) create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable/ic_keyboard_default.xml diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_keyboard_default.xml b/android/KMAPro/kMAPro/src/main/res/drawable/ic_keyboard_default.xml new file mode 100644 index 0000000000..9c45b680fb --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/drawable/ic_keyboard_default.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_keyboard_physical.xml b/android/KMAPro/kMAPro/src/main/res/drawable/ic_keyboard_physical.xml index 422e2bd5ed..eb3bbe260f 100644 --- a/android/KMAPro/kMAPro/src/main/res/drawable/ic_keyboard_physical.xml +++ b/android/KMAPro/kMAPro/src/main/res/drawable/ic_keyboard_physical.xml @@ -5,126 +5,74 @@ android:viewportWidth="24" android:viewportHeight="24"> + + + + + + + android:pathData="M3,11 L21,11 L20,21 L4,21 Z" /> - + - - - - - + android:pathData="M3,11 L21,11" /> - + - - - - - + android:strokeWidth="1" + android:pathData="M5,13 L6,13 M6.5,13 L7.5,13 M8,13 L9,13 M9.5,13 L10.5,13 M11,13 L12,13 M12.5,13 L13.5,13 M14,13 L15,13 M15.5,13 L16.5,13 M17,13 L18,13 M18.5,13 L19,13" /> - + + android:strokeWidth="1" + android:pathData="M5,15 L6,15 M6.5,15 L7.5,15 M8,15 L9,15 M9.5,15 L10.5,15 M11,15 L12,15 M12.5,15 L13.5,15 M14,15 L15,15 M15.5,15 L16.5,15 M17,15 L18,15 M18.5,15 L19,15" /> + + + + - + android:strokeWidth="1.2" + android:pathData="M8,19 L16,19" /> - + + android:pathData="M5,21 L4,23" /> - + - - \ No newline at end of file + android:strokeWidth="1.5" + android:strokeLineCap="round" + android:pathData="M19,21 L20,23" /> + diff --git a/android/KMAPro/kMAPro/src/main/res/menu/nav_menu.xml b/android/KMAPro/kMAPro/src/main/res/menu/nav_menu.xml index e1dad222af..3f1f7a6e5a 100644 --- a/android/KMAPro/kMAPro/src/main/res/menu/nav_menu.xml +++ b/android/KMAPro/kMAPro/src/main/res/menu/nav_menu.xml @@ -30,7 +30,7 @@ From 32b383f3f3899c913adec30d905cc9abb1ec8cf4 Mon Sep 17 00:00:00 2001 From: Mengchou97 Date: Mon, 20 Apr 2026 14:28:32 +0700 Subject: [PATCH 041/140] updated icons for side panel and titlebar --- .../res/drawable/ic_action_overflow_mode.png | Bin 0 -> 5495 bytes .../res/drawable/ic_action_overflow_mode.xml | 4 - .../ic_action_overflow_mode_small.xml | 7 + .../main/res/drawable/ic_keyboard_default.png | Bin 0 -> 10039 bytes .../main/res/drawable/ic_keyboard_default.xml | 53 -------- .../res/drawable/ic_keyboard_on_screen.png | Bin 0 -> 5150 bytes .../res/drawable/ic_keyboard_on_screen.xml | 123 ------------------ .../res/drawable/ic_keyboard_physical.png | Bin 0 -> 11953 bytes .../res/drawable/ic_keyboard_physical.xml | 78 ----------- .../KMAPro/kMAPro/src/main/res/menu/main.xml | 2 +- 10 files changed, 8 insertions(+), 259 deletions(-) create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable/ic_action_overflow_mode.png delete mode 100644 android/KMAPro/kMAPro/src/main/res/drawable/ic_action_overflow_mode.xml create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable/ic_action_overflow_mode_small.xml create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable/ic_keyboard_default.png delete mode 100644 android/KMAPro/kMAPro/src/main/res/drawable/ic_keyboard_default.xml create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable/ic_keyboard_on_screen.png delete mode 100644 android/KMAPro/kMAPro/src/main/res/drawable/ic_keyboard_on_screen.xml create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable/ic_keyboard_physical.png delete mode 100644 android/KMAPro/kMAPro/src/main/res/drawable/ic_keyboard_physical.xml diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_overflow_mode.png b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_overflow_mode.png new file mode 100644 index 0000000000000000000000000000000000000000..b78c32e85f8a91dbb4c3d2467572a38824e475bc GIT binary patch literal 5495 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 I4wL@*9~*X$!2kdN literal 0 HcmV?d00001 diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_overflow_mode.xml b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_overflow_mode.xml deleted file mode 100644 index 5b1e82c706..0000000000 --- a/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_overflow_mode.xml +++ /dev/null @@ -1,4 +0,0 @@ - - \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_overflow_mode_small.xml b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_overflow_mode_small.xml new file mode 100644 index 0000000000..db1ba43a50 --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_overflow_mode_small.xml @@ -0,0 +1,7 @@ + + \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_keyboard_default.png b/android/KMAPro/kMAPro/src/main/res/drawable/ic_keyboard_default.png new file mode 100644 index 0000000000000000000000000000000000000000..1f08b2e9e3bdb8cc7d0f8a11c1868a5492c0398d GIT binary patch literal 10039 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 J - - - - - - - - - - - - - - - - - - - - - diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_keyboard_on_screen.png b/android/KMAPro/kMAPro/src/main/res/drawable/ic_keyboard_on_screen.png new file mode 100644 index 0000000000000000000000000000000000000000..c8707e943de32a13d2792a97cc38cde5cdf30659 GIT binary patch literal 5150 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 literal 0 HcmV?d00001 diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_keyboard_on_screen.xml b/android/KMAPro/kMAPro/src/main/res/drawable/ic_keyboard_on_screen.xml deleted file mode 100644 index 608500ee7a..0000000000 --- a/android/KMAPro/kMAPro/src/main/res/drawable/ic_keyboard_on_screen.xml +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_keyboard_physical.png b/android/KMAPro/kMAPro/src/main/res/drawable/ic_keyboard_physical.png new file mode 100644 index 0000000000000000000000000000000000000000..4ef7ec9fe1096d4a9f980ec2f1f9cb51cb5a94a3 GIT binary patch literal 11953 zcmd^lXH-)`v~CInLKP6C*FdBx`;?edMD8+f;2-_ngxW=5vd}?C`}ZU zE;Td}5C~xC%Cv^{<-(xT`S2snc1_?oSEHbe>=h4^cph*F9Qe!Vn!KWu>gS} zz%2ws4+AcL!-o!l3p~K^Mi2<}NA=$qJpAfWC(y_feAO=4(*I6ym}{UrC@d^Y&eP93 z$jvptUCuwyBX?bk7X%Upp|0pzh3BnJMZ9x#kJ#JX_?DNZK##yG;4n$|A(?tL^p9Vu zgZrS5WlOE&ZepcMWh?8)M{W{ALHuV*8SZ10B+n}=(VyQIc7q^XSww;Y54)n|Cl|uH zVy0aS>)H$J3pxUu-|lYb)osKU*2VAAqVS0SEuUw>nWmTULqFc^eufdbU5tJP8{8IA zC_xs;=AnaCLHHuOP|yxEbUC3C3%{zI=*Ahq5g-`AKneaCj6g}1GlmOL3W={Tg+b?T zke?98=nD%GC|`F5%0;qShEN*1W081J8JmVj)IaDU!g0XInZ|$wyM%wf z7EV4cPB)JQMhrQ;2gliJz%yN;!l-W-;Cqs$ZWDI@I*gsc4d?T5UuwZ=MZbQ+_M(>& zR$0s#gi60H_8u_$#y=t%O-Ou}>E*?rAVZ6~Xtw@i(gT41ZT%x^wV<03QkWo?M0*FE z3-N}gN8MtmVL2HqePYX&bzsda1Pb-L$M*z_jGTkH#FnF&i=>AiiHB6kVq!3BaID$=fm?ttm+!}-I-FO+( zMY%>!CvF%aMG1A4jQns16T79>zc*J64m;{`gh#8lqQ1mQRCeAXlgEUT5XmqWQDfHk zIa^50*D>?{gc;6XrguRtSWcq7Sez=J*GndVCO~M>%+KJk>l~rP)D!57Aiir7N-dIj z28w0~2S#3atvtuRvfJBki&Ozk;o>3Uo>y8hsqBK?QZekx8b}*Rby1r}CD@MP>)l1@ z=9B~BMkq}c;wW;gG(c`e<{V@<+w_%QCpfQC#_7m1_+6g>uxlZ?vjd_sK7)mWSN9ia_7Vo_&ps_gLH>4jbLVs*ptVe`>Q zCCnjxyXpFR0v&e$^F0&eU6ZMs$@EQ6xIu!4K+4;CzQTHil z28uq{OMSRU{FI^lMne%$^s9Bc?q+wTF{hvrlw;}J1I90`S^3<6j=pLx2t?Ms$2tyV zmqH9Urq4@iyM*2x0~sdHfxhH-o6<>?$X};jGioAPMt=WOR0B>s+rhb@i>*XVuG%U` z19s;Q@*y*(C~FOMl0F(pe$3wi7%b4}^;q(s41jv*En|=FUb`~dy)5?`Zd4P^T9AJR zc<#-&JhK+jRp}#AM~gGTQDF3WT}(q%YgCa?G)j(6X1G7T=df|Vk;tmNZbl7~oo%we zuJKe=tvVQjAD@_7IP3^yJ2~^ccUr$m!0I47_`QKZmUht;~p>CS)&IREMC+5!?iB$>ov;NvWH)j7Aix8fP+wRBn@R-y%jqS zdfW2AwyK+5c97_zj(!XL+o{tyZ(tIe24H{D3jXG_Eofk0IB4uOHPswXl8zvl-84JH z75DU(J!81$tjfsyZq3Y)z8q*Y7x%d(H|gGVtQG4;K~ar!C6U&=@{smFaEv6;o4$*B z2iIsS0a@kjscI?4!D9tsa^P6AyEF*N=az=2tBc=#h7HTKmq6JoB2-R{H8C?`R|JJO zI8WsR?Jh`Pwa}z6V62?ZEuUKiZ2dc4K=@lxMb1~h0_N7s2UN86=Jr_EC!QSUzrH=q;2kAYIlvV`zydOg@)G6)q42!j zfD`0x{hcLu3SAbNB}%F|puO zhgJ*zl}->QxRPLS;(MVOJO&CEsNth{xmUS7g=jH1VFQ@{~ zn2`FJvFyL?RR~o6YrZDBCP)+ao-E~756`07)KRDTYyKIQp_-t{RBS0cR zhvM)vc<$~l%;f^*4%v`gNo@L$?nw*sWpWg;8e{D~NT_n;!%(qiplje<&c3UFr%eSR zsh^b}2JzH}dV?B3DXIQ(pN z8INB2XdIW%b7Cqn>G0|;6n<#EO1!l)G;I0GLD?WD<_B>Tw=2^qmG=aJs`Y^#(bnJ< zdR@YP2H$US0FUtl{&;&J!4RMEol1J=z+wo>% zn>ea0_dVu8x7HJvb(jm9JW8zidYFkI^!Pv3n$ ze2O5XYng>xuK%*qH2euNrmX@yJqr-RBSOkbFxpt%;<4~D573CjWLE$s8$o!^jBy67 z0*#5%AUD7*eGvKBU}+E6g2~W0Asp^UfbFg;NumZGCxhRdktoPzs-XS)!s!W_dDNf2 zpXQ3d6N?L=8JJG4%llewvvBpzNb+<+laQSaFvdb@p5FFNBW;7k*WPy4m#EVB{*bOL z(_0Ec2$>+e(I)twXbs=mPg*1V953@97Jj;;g01wrcI_YoZX$`e~7}L z{n-9AF7L=`<&~<0@ujr7($kKp6MWIm586DV>DoZJYs&rGCz3&WYV2A583;mt5}lvK zM$UdJA(4mfYp-2*-R;=7*YQP<8xR{qAA?Zu%)cKhas4o{iw4#sbVqP2B|pCC{MR#_$Z!X%f zTJLZ^1@>Q!p{eli{K}tMgi=l=_penNSqDi4MvDBG>F_5_&d)C}7GzBJTZXY$ znFw)|msl=GXWP*HYCIkK*4Hzrb=A4I2I~yumrRG?9Hy_r4#TA_-QO8pbSUbsDNCUW zf;mQ|F@cN|5s5W|pS4II?ZX^#8pIa7l!uf zmXR-HR3 zOAb<`Fp}wADjjH<2Wc*kMzK&tH>+)?X!y;vt8cDBqtE5&AGyV-QUnGHYszwHqZ25> zA%C2$8L@R(R<@K%jb}{JNtDK>oTiX!#Mje6nU~MX(&fk|xFG*Tk0C)s63=^N)%+kl z&{dKmh3mz*by+K;Ig~*dO*tU&}Li z<$gZi^(>|EQ@7&PNoJp{w$I=XPxjhR9&tRugxxOn7tZCF7L?k|Kc!pbovy{vbsG)E zC|6Dyc@MkdCM=p)oi1@(-dYu%G&0RUm9WN7sOjG-u}1mO`-{xT-#@v$gc{)6UtFWQ+_i@HWk)6&!l7>jLuioCK`Ie@xO32e=Zj(Je=^MK- z3lYjbCPK)5{UB%u`@q+PAcIGfy9E4Lml9$pK`Mdy7Hr?ffp^6v!en6_Hh9$z@-}#C zig+)<+i?&n_G{Y6LBEZ*PS|0`Ip;;n(@sX*DByhc!>ft`ss;bQsi`?p()UCnmVAHG z+CBeAkxuM<>U~(w6d~j!UOWA4-=dSR459ki`A&C4(>=cZSmsmrH_sn&^kUWqvJUaz z;MuRxqmz8sN@T-Snz?@SnmS}QjkKLexC)3RKeTFXz4?&^*IqfGvfIh#*&EYyo}6N= z!i?-ob(o3e3DkLMbgSgHp-*JyXIPDxyF459+(we!%q4?#4;#YM&#dV-M{oT-Dm>K{ zv$f0;mzuQI2EtVE$o%>YgvuMvX$A#IPD{X252JAwp8^NiC=PLYP?A-u3 ze_|X_GY8z{wnQ;Ap-Y^iGXEmu>TXl3OANH5hi!FL^ccDdJ@I7;P0*mik6w09l+Rm` zqS8;a_ccbeQz+6Z#UDz$yiwkfmUC;V;2pEpRHA0Q)C0p>0ge%j{rxT*3PTO;Sh z*UuBx^GU&JI@K*BAK;#5H*I;Ui(ES% z#MEunz!dDiOs+VDd9BrOUuOt8>C+w=EZwf19@@~l<3~6r z#7pTqa$&x-k|1bfFgcR`Z^}ow38%{indN0U+2My~;?uF#Q$^Z>xsesRs2DmN@Th*a zX;Z^u$*#z+wKclXl*JG`qWGgBv+E6_A%3h906i;mI@Ym$`x+c=P+~FKBI4`U3uiPN z{_!cH(Rs9ebExZ-FO40{xf&l zjR}OV$p2XVdR)-mDJ>)Iu3Wa&3>b)vAAtXzHzHZpOFHI-A6m3`p|4SnmUoL>cLNOhR+NeC)%6ej=?w(#-a4zYyXp2k}vuFt%9O=UzXtg=5bt*8i4 zPSN)3UR8!x`kCDkSLcG@tK6P4AXOv#b#%i^r$KO>x^J`mvgqWFl%v0YQO?#lOc+lX za;r@fCz`@n_>7lBNKy0K1V%@FN1My7iLJe0f4!Xew&1D$r(vY5>qLLCQF9d*Gl$jZ zDarp#jMK{H(Hbb#jvAzM?_ABQJM_$t6Wm3ZkpM!vJMI=n!P8*b9_(@bS2KQqQBT|O z8KIQ*dtJS;q3~j1L`$qRS)X8t`ME zFw*$PLF^XeEC4+jt(e#Lbd+25K8*&oy_{gFof5D!kP4_T8R;BjjmPmlhqXU?d zz@ENt#NuO)p0Fr?lh^QX*AiO%i~b}fZ9uqo(`{G6vnZ1$QmP=h4^y5&_+7Csy+Gio zZ|&)Z?ZkdoT1eOl)aF@w61J$g`{Pxgu_>y&NT>0}{@v0t}@Jm~$v)GZv6UR^b#75MFfQ<_C z-USNZoU=r%pgKpmuICb@`j$+<`2g7L)Zd&CdF15ksW%DwIHXQ<_S@sbgt&?sx+6x) zxX&=t3=^piY0J^}IwwSz8OTsgSu|!YOF)E#k7PUj_OjVxhoTeTd23lwBT1P#@sEL# zdfdtu)qX$A?Vt){?iD|aKD>)y2(Tip?RxOLx=i9$p8S@92_m(8nh+LFgb=oq$0gT~ zQzK|+MRK|bOII#HZ{#XmGA=%VA9<2?wF@Krd#6XkN$y)b#G=FryDK>{{&Li4)(^U` z>AUuWU5QWY#w+8E$f&Q=YwTBVwU2 z`uA;eH*TU(|Di{Q#v%g-feNwy!*B`tjpjTG|ETAG;)wk}a!&tmGI^m`%yIXzEl3l} z2Ohk;78J)44hcWg^qM+^=Vd(NQFvf}Sj5#(k242?qz!DbtVi}p15p~gjN520%Opl$ zUW)@2_-NM`snR&<@3oacUfYk2C=(sXvE+ggOSG3(xzH-dmp6l1HlS_#t^-+Oo%^6? z!_P}$B=w8Vl2?;pIt&gl8RR#Qy)zV9mq9IY=7|ELCypaLv89kMmZ4R|Qu~vNz#c3> z$aBRB>;R6=`|dE@VMpd5X5#oQhhpU^A(x?!$V2v&4%#AZXI0rOa5f}bFbKacwL8k?bPfLjrM`D77^lt6GCfnS&`Zs6YCivru*@MvyC|j#B*Sv^0|Vlf!GzhW7`w<*D9=eixa(+Jb)Z zDY^IVTAMfLAbj^1dgS=5f^^7B@87SEs)^RP-lAp!(`jk#1JO{!E1lg|-KW4E zkW3UW`^4>}rGH!y;g!jJiEfW)@l-w1jb|1g%_8*0yx(xn@?V_v2R}1&3F{@CPpCLQ}moe~>Lt0R7ryk~v_Y8Zkz~mAo!R$T{%5X2*MMx7p#bW@13Ybf;$b}0{sXt2d-FE* z%(eVjHtKUotLmoCbPnJJ*8rETc}60NkjvlYDv(M|hI7alTXnVMe%%m*@L@(wM89s? zUW*c+K3Qe=Z!dc@a%@C$U?LdPH5fM?+Z3JjS^7JR)k*g?j4A!2ejS^A8ujh&_L}YR zP>aYBuV0sMV_DBk1V^%nZ`}mFDyY)FJs9%9@HjZw(c9l^>de2upf9B4q|g3Fzn*82 z)(GE6*o%-SXr#j9tN)z;c+HAoVamWzukz6l9 zSDQ=qM1Xn<=Wf3Gd+YN=yXe30&2iV7_~r9q>D*0{Od3H(-1wornlz@IV`Dz?^TcSk zgP!_s8LcYlUY@N6Hg=NfE2=dWT{#s@G5M0gX7?Chs&K{~; z_AcY^itMTbKc1H%ZM_KDs^;0*JR5NH(Q(q}e){Y(%fm*dL+%6*jHU9QB|73D&yF(t z#T05hrqE+7^^t0B4SNxLpyG?C`;1@Y@jv+Dg!`lS&Tt8o^?nIJ)7Sl7{C>5G)4b)^ zw7Q0GbNR*p2hV?UNlCg{k8i zl-~EjQ?^gTl*)iGfPF}DAUq|y+-XNcn-v#m7bNnSak?@*=3?K7cuTh z&5rdtMp@3E!xa~5J_R%|Qf>C*%b_^#UhL^Boxyj0tVhqakPpkTj$0WjMfF&JcBf3V zBVcl`ouByocLl;Cwvf@XUW}tV8R@h5`8`yG{{gVFZ#o|kUw%5iJZ@DBbE)Cz_HjvH zQDI&_yVBecZu}bi0s8=s-tH5Y5ghs=WwQ-WcbQ~$xbiz`Rh{j}2(D$uurvx*ncsAd z-N`%pKBafpf!xOxWbCR<_SLSHb^VC zOI7t_B`a^)g7WD82NKo)g#*l{xA+bMu_*nk1^90##Q%%u{C|1#T54d8 zC7^T=O$gv#u?d`w^-MW*S}c={4)j_aB3(AdG%TP{k)Z}Q1;SiI7IYg7KL>`jLc4FfPl0)W68$E}$GLIz&nC37fRW?;##RZYH$alyV zE+`zoeXUNwnHrVZJcZ1Fs?uAEz%D?V1e$Uv`Klv6FEm~A@?Jjj#n!={{mL}5pzW}o zZu9=ZS#JdTo!c$A@#NCwxy^+A$zYk!JkL4xB)y zP}P1bOXn!|zUJO*X&Z)aimrdgRQ0uy$copw3R$Uknrfie*f@z`a`Oq<7F=R+oV{xp z40aavbsKcfW3I@3se>_tGAa$-=9Y8=Q*0M%1u5H1D%9kw#Cy0P3omc5u4tZj&~PKu z9?aDk)d3TJb&q%>AxKEZPuStPk^6;Z)|`)XETqK-s}}li_Jji_v8#E8<9dM_l=%D9 zxTHE|MgTsbjR88B#ea_YDU{<}% zvt5gl$`ic#26Xda6{LexG z_KFO9KA+dtOu}1iS8hi^tDiJ|a3hRHMDvl#BWjOSmi^Rh6q)w``1TfI5Ib1Lnr-?* zDE{iCjlk-m@TTAq!7avuK`8NEz9KP7%vbtNPF!!TPI->TFNa}CW+Dygc)sQ5*&I$+ zO-G^*TLZ_$S<-^O;vYUwO^c}xjgc_1Bj%BBe2=#~KaCJ2{`D7F6&hCAXaG1*LZqX_ zigc&Gu04qF%9tsG;);DPrAtobV*~Cd%AV@M*6@^M|@N2onHNW5ZQ60I9Uje z`^q{3k}*v)bXI)Q_FxO`&$94~ej+?=3(dlcFo@{~vtAiX8Dd!%t9Ub7#vKB z?yA{%Wz2v94RE)^UE*@a~d98ls zEV-|HLhJe8>O!O93IzYW;uK4??v|?kJ*Indr@PO;2}A9`U2MDiO*wAlOH#>;bm?u% z`-h3N%)j!OB3L=qAZ{E(umB6@mG^XZ(${00%k^R&JrzB9dO-rZJMyCK6lFD|zH%=_ zp<^wp`cC&@zBT*AMg3cnfl5eJYPZ z;-36KUu(-n_gqXNhs`Auci!1bj0M%v;CTtP`divziZy$#@j=L&Z2Yh61$DBn@IfN-8+o9RiETQ-RAPARKX+fY`W&Zox$PcZUnT~_2#9zG3F66giA|0%SnCLx~YM1F6NZ zU`9@PvHH7=q+u35@aw5tinE@Z&lbWI0#@k*E7{T5pq?PsC=)u+X!_1<7jP8Ns$m6lHN&^dltna0s}?}{l>k)W*A;S5!fq{?5=tJ%p;5}E)10#4UyxJ|x#oPv5kLlj%9j z6M^F!xXu_Z*Bes=t9Ik|G{F0?9tk$3ZCO(Nj7t5zNxq}YR&n*wY_o^CjFYe<{NHL) z9Nk{&KH3xw1d`lAV_g?K{&iZV=>suvo?}nIK^1DVb0#%mY zh+m2*^S-&?#es^8Yvxn0`%m=4BR$r^@iOjbXmx+a-7mIF0O%&1p#GwlN(<(mCn^M zjcK~;;Su7LC7hznQci^Vm-|4LPU+lVepeo66n^|83sHGNKzc*sv%u|Mn+8xW<#Bd!9WNTHJ3oB*!p;H}2_%X8L% z-=6*}ZiK&3$_kP<_tVl6j!8Ive>VsICUFph>M74ac!qFbI3zV;1=+1%^nCQ&hLd55 z_V8*40NDOij?py_{bPTHbmR2VYH!BznHKCK&eX?f?xr~En+jYVY-a?(oBjh?8d`2x zU5k@XzhQ#3)5+fShWNQy^3o%EXZF9zWDS4Z)K&N8q* zL~Dz>>M)%!%FEsj+X!R^tEI}l$4~ajB-rY6f_Ys6p1F$AX z=lv(=59Lk+=D;o=a)d4=n%=S{^O7|2YcB8r|HAN`0AVeSadOZ-r5C)ArwZrKdn z0BVqECABfHDVxf|oUVma6`lhyy$*fe(||)fLPbx_wc(?(o4@+7x?gw zP#08}?096^CK1M21Fu1oVgPiXa6+dpH{5C2j*JtTjqOcI*cC%rATRRmanEvHVkhun z{K)5sro@JXT>#)js{x*tIQvyvg-{Zg2 z_!xz6SODPvIuq%_P7hsNx2A3E#u^Btc6+iKNQCVQ5s`+`KijL>pDa3nzk;VN?w$e~ z)WJX=B-*0(h|Y0FrbF8XWF&6+0f1aSM`CxO?XtDo1Ikq2%i+VWyQBy}2nJ<|FLBFD zPtM1{W5Kg8<0WwtFM{<&Q5Hs1#6}D&*eu()$P~JcC9-0d|5a~EVv(3nSTq_5S*!;A zr6S3PL}~g#x>3SKpv-_%7dj1_1WKHoLIiHJf=(F6%7R6K5>m1bIsI(pcYlSD7RdL} z2e_+e3Sc`$F(W=%2)K&~C4}_hM3=L#*g3=(By21a4GoY|U<+ra_5qco#W?A)4GEdY z*}k_5pO&r4VorQmqMs?4%r%jkft4S%AhC{AgX?Xe!*|AweJD5t(l> z3Y1f6*Q|+#A1Tb2s(Wxe87VZO(m=fj;YJl*8(ki%X=WyLyczu48vy>fHlTO1On^$2 zmx2hUJ>543)!WuUV0Nimj$BXFq~~C~SO^rlmRSQOa`GY-ccLtT8km*8AMf$HF3*0z ze8A>YOdu$WQyWMeO%=FGyuwMd{q|+akVt6Fn*`zxu^cOaxk2Q@O~6(&&}e+dk8Pqp z?qRqfXr0Dyb$=){`ZtO dTgV;>LHn@T(`y|uK)E>xb=CAr)n%9a{|AE87XSbN literal 0 HcmV?d00001 diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_keyboard_physical.xml b/android/KMAPro/kMAPro/src/main/res/drawable/ic_keyboard_physical.xml deleted file mode 100644 index eb3bbe260f..0000000000 --- a/android/KMAPro/kMAPro/src/main/res/drawable/ic_keyboard_physical.xml +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/android/KMAPro/kMAPro/src/main/res/menu/main.xml b/android/KMAPro/kMAPro/src/main/res/menu/main.xml index 26b9c5232c..9202c14e1c 100644 --- a/android/KMAPro/kMAPro/src/main/res/menu/main.xml +++ b/android/KMAPro/kMAPro/src/main/res/menu/main.xml @@ -76,7 +76,7 @@ From 4d48850dc32172c19b6f048738d9bc9984d2cd89 Mon Sep 17 00:00:00 2001 From: Mengchou97 Date: Wed, 22 Apr 2026 21:31:41 +0700 Subject: [PATCH 042/140] completed adding separator to side-panel using group --- .../com/tavultesoft/kmapro/MainActivity.java | 2 + .../kMAPro/src/main/res/menu/nav_menu.xml | 172 +++++++++--------- 2 files changed, 85 insertions(+), 89 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index cda48deae4..5a8e0f0a17 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -92,6 +92,7 @@ import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import androidx.annotation.NonNull; import androidx.core.app.ActivityCompat; +import androidx.core.view.MenuCompat; import androidx.core.view.WindowCompat; import androidx.core.view.WindowInsetsControllerCompat; import androidx.drawerlayout.widget.DrawerLayout; @@ -297,6 +298,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene navigationView = findViewById(R.id.nav_view); navigationView.setItemMaxLines(4); + MenuCompat.setGroupDividerEnabled(navigationView.getMenu(), true); navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() { @Override public boolean onNavigationItemSelected(@NonNull MenuItem item) { diff --git a/android/KMAPro/kMAPro/src/main/res/menu/nav_menu.xml b/android/KMAPro/kMAPro/src/main/res/menu/nav_menu.xml index 3f1f7a6e5a..a3bf5b3242 100644 --- a/android/KMAPro/kMAPro/src/main/res/menu/nav_menu.xml +++ b/android/KMAPro/kMAPro/src/main/res/menu/nav_menu.xml @@ -4,108 +4,102 @@ xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity"> - + + - + - + - + - + + - + + - + - + - + - + - + - + - + + - + + - + - - - - - + + From 5f8c38e674f12f34f7b6371cb74f59808feb80d2 Mon Sep 17 00:00:00 2001 From: Mengchou97 Date: Wed, 22 Apr 2026 21:42:47 +0700 Subject: [PATCH 043/140] updated the hamburger icon in the titlebar to switch between theme --- .../src/main/res/drawable/ic_action_overflow_mode_small.xml | 2 +- .../src/main/res/drawable/ic_action_overflow_mode_tinted.xml | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable/ic_action_overflow_mode_tinted.xml diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_overflow_mode_small.xml b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_overflow_mode_small.xml index db1ba43a50..b9581eec2c 100644 --- a/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_overflow_mode_small.xml +++ b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_overflow_mode_small.xml @@ -1,6 +1,6 @@ + From a3781a859c921c5ef94bd5afe4400913471baeb8 Mon Sep 17 00:00:00 2001 From: minanlynna-code Date: Thu, 23 Apr 2026 21:55:37 +0700 Subject: [PATCH 044/140] Update dark mode handling for Chrome WebView layout --- android/KMAPro/kMAPro/build.gradle | 4 +- .../com/tavultesoft/kmapro/MainActivity.java | 665 +++++++----------- .../layout/check_chrome_webview_layout.xml | 4 +- .../kMAPro/src/main/res/values/colors.xml | 2 +- 4 files changed, 272 insertions(+), 403 deletions(-) diff --git a/android/KMAPro/kMAPro/build.gradle b/android/KMAPro/kMAPro/build.gradle index 77ab2fa736..df8576d044 100644 --- a/android/KMAPro/kMAPro/build.gradle +++ b/android/KMAPro/kMAPro/build.gradle @@ -32,7 +32,7 @@ android { defaultConfig { applicationId "com.tavultesoft.kmapro" - minSdkVersion 21 + minSdkVersion 23 targetSdkVersion 35 //println "===DUMPING PROPERTIES===" @@ -184,6 +184,8 @@ dependencies { implementation ('com.github.kenglxn.QRGen:android:3.0.1') { transitive = true } + implementation 'com.google.android.gms:play-services-maps3d:0.2.0' + //implementation 'com.google.android.gms:play-services-maps:19.0.0' } /*def void dumpProperties(it){ diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index 5a8e0f0a17..6c7387d61c 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -92,7 +92,6 @@ import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import androidx.annotation.NonNull; import androidx.core.app.ActivityCompat; -import androidx.core.view.MenuCompat; import androidx.core.view.WindowCompat; import androidx.core.view.WindowInsetsControllerCompat; import androidx.drawerlayout.widget.DrawerLayout; @@ -101,6 +100,7 @@ import androidx.drawerlayout.widget.DrawerLayout; import androidx.core.content.ContextCompat; + import android.provider.Settings; import android.text.Html; import android.text.SpannableString; @@ -137,7 +137,6 @@ import android.view.MenuItem; import androidx.appcompat.widget.AppCompatCheckBox; import io.sentry.android.core.SentryAndroid; -import io.sentry.Sentry; public class MainActivity extends BaseActivity implements OnKeyboardEventListener, OnKeyboardDownloadEventListener, ActivityCompat.OnRequestPermissionsResultCallback { @@ -182,7 +181,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene @Override protected void onCreate(Bundle savedInstanceState) { - // setTheme(R.style.AppTheme); + // setTheme(R.style.AppTheme); SharedPreferences theme_prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE); int savedMode = theme_prefs.getInt(KEY_THEME_MODE, AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM); @@ -207,8 +206,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene KMManager.setDebugMode(true); } - // Verify WebView installed and enabled before attempting to initialize - // KMManager + // Verify WebView installed and enabled before attempting to initialize KMManager KMManager.initialize(getApplicationContext(), KeyboardType.KEYBOARD_TYPE_INAPP); KMManager.executeResourceUpdate(this); @@ -216,8 +214,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene DefaultLanguageResource.install(context); SharedPreferences prefs = getSharedPreferences(getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); - KMManager.SpacebarText spacebarText = KMManager.SpacebarText.fromString( - prefs.getString(KeymanSettingsActivity.spacebarTextKey, KMManager.SpacebarText.LANGUAGE_KEYBOARD.toString())); + KMManager.SpacebarText spacebarText = KMManager.SpacebarText.fromString(prefs.getString(KeymanSettingsActivity.spacebarTextKey, KMManager.SpacebarText.LANGUAGE_KEYBOARD.toString())); KMManager.setSpacebarText(spacebarText); checkHapticFeedback(); @@ -230,9 +227,9 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene navView.getLayoutParams().width = width; navView.requestLayout(); - constraintLayout = (ConstraintLayout) findViewById(R.id.constraintLayout); - // setupEdgeToEdge(R.id.constraintLayout); - // setupStatusBarColors(android.R.color.white, R.color.neutral_2); + constraintLayout = (ConstraintLayout)findViewById(R.id.constraintLayout); +// setupEdgeToEdge(R.id.constraintLayout); +// setupStatusBarColors(android.R.color.white, R.color.neutral_2); // START OF FIXED STATUS BAR LOGIC if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { Window window = getWindow(); @@ -247,7 +244,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene // Check if the system is currently in Night Mode boolean isDarkMode = (getResources().getConfiguration().uiMode & - Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES; + Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES; if (!isDarkMode) { // LIGHT MODE: Make icons BLACK so they show up on white @@ -260,6 +257,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } } + toolbar = (Toolbar) findViewById(R.id.titlebar); setSupportActionBar(toolbar); getSupportActionBar().setTitle(null); @@ -267,14 +265,14 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene getSupportActionBar().setDisplayUseLogoEnabled(true); getSupportActionBar().setDisplayShowHomeEnabled(true); getSupportActionBar().setDisplayShowTitleEnabled(true); - // ImageView logo = new ImageView(this); - // logo.setImageResource(R.drawable.keyman_logo); - // Toolbar.LayoutParams params = new Toolbar.LayoutParams( - // Toolbar.LayoutParams.WRAP_CONTENT, - // Toolbar.LayoutParams.WRAP_CONTENT, - // Gravity.CENTER - // ); - // toolbar.addView(logo, params); +// ImageView logo = new ImageView(this); +// logo.setImageResource(R.drawable.keyman_logo); +// Toolbar.LayoutParams params = new Toolbar.LayoutParams( +// Toolbar.LayoutParams.WRAP_CONTENT, +// Toolbar.LayoutParams.WRAP_CONTENT, +// Gravity.CENTER +// ); +// toolbar.addView(logo, params); getSupportActionBar().setLogo(R.drawable.keyman_logo); getSupportActionBar().setDisplayUseLogoEnabled(false); @@ -283,50 +281,36 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene getSupportActionBar().setDisplayUseLogoEnabled(true); getSupportActionBar().setDisplayShowHomeEnabled(true); getSupportActionBar().setLogo(R.drawable.keyman_logo_mode); - // getSupportActionBar().setLogo(R.drawable.keyman_logo); +// getSupportActionBar().setLogo(R.drawable.keyman_logo); getSupportActionBar().setDisplayShowTitleEnabled(false); getSupportActionBar().setBackgroundDrawable(getActionBarDrawable(this)); drawerLayout = findViewById(R.id.drawer_layout); drawerLayout.setScrimColor(Color.parseColor("#80000000")); - // Initialize drawerToggle WITHOUT toolbar to prevent it from showing up at the - // start - drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, R.string.drawer_open, R.string.drawer_close); + // Initialize drawerToggle WITHOUT toolbar to prevent it from showing up at the start + drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, R.string.drawer_open , R.string.drawer_close); drawerLayout.addDrawerListener(drawerToggle); drawerToggle.syncState(); navigationView = findViewById(R.id.nav_view); - navigationView.setItemMaxLines(4); - MenuCompat.setGroupDividerEnabled(navigationView.getMenu(), true); + navigationView.setItemMaxLines(2); navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() { @Override public boolean onNavigationItemSelected(@NonNull MenuItem item) { int id = item.getItemId(); - if (id == R.id.nav_toggle_show_osk || id == R.id.action_get_started - || id == R.id.nav_toggle_send_crash_report) { + if (id == R.id.nav_toggle_show_osk || id == R.id.action_get_started || id == R.id.nav_toggle_send_crash_report) { toggleDrawerSwitch(navigationView, id); } else if (id == R.id.nav_checkbox_enable_system_keyboard) { drawerLayout.closeDrawers(); startActivity(new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS)); } else if (id == R.id.nav_checkbox_set_default_keyboard) { drawerLayout.closeDrawers(); - showDefaultKeyboardPickerFromMainPage(); - - } else if (id == R.id.action_update_keyboards) { - drawerLayout.closeDrawers(); - KMManager.getUpdateTool().executeOpenUpdates(); - // Dismiss icon - updateUpdateCountIndicator(0); - final MenuItem _keyboardupdate = navigationView.getMenu().findItem(R.id.action_update_keyboards); - if (_keyboardupdate != null && _keyboardupdate.isVisible()) { - _keyboardupdate.setVisible(false); + InputMethodManager imManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); + if (imManager != null) { + imManager.showInputMethodPicker(); } - return true; - } else if (item.getItemId() == R.id.action_settings) { - showSettings(); - return true; } else if (id == R.id.nav_installed_languages) { drawerLayout.closeDrawers(); Intent intent = new Intent(context, LanguagesSettingsActivity.class); @@ -349,17 +333,17 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene // Same entries as in KeymanSettingsFragment CharSequence[] entries = { - getString(R.string.spacebar_caption_language), - getString(R.string.spacebar_caption_keyboard), - getString(R.string.spacebar_caption_language_keyboard), - getString(R.string.spacebar_caption_blank) + getString(R.string.spacebar_caption_language), + getString(R.string.spacebar_caption_keyboard), + getString(R.string.spacebar_caption_language_keyboard), + getString(R.string.spacebar_caption_blank) }; KMManager.SpacebarText[] values = { - KMManager.SpacebarText.LANGUAGE, - KMManager.SpacebarText.KEYBOARD, - KMManager.SpacebarText.LANGUAGE_KEYBOARD, - KMManager.SpacebarText.BLANK + KMManager.SpacebarText.LANGUAGE, + KMManager.SpacebarText.KEYBOARD, + KMManager.SpacebarText.LANGUAGE_KEYBOARD, + KMManager.SpacebarText.BLANK }; // Find the currently selected index @@ -367,31 +351,32 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene int currentIndex = Arrays.asList(values).indexOf(current); new AlertDialog.Builder(context) - .setTitle(getString(R.string.spacebar_caption)) - .setSingleChoiceItems(entries, currentIndex, (dialog, which) -> { - KMManager.setSpacebarText(values[which]); + .setTitle(getString(R.string.spacebar_caption)) + .setSingleChoiceItems(entries, currentIndex, (dialog, which) -> { + KMManager.setSpacebarText(values[which]); - // Persist the selection just like KeymanSettingsFragment does - SharedPreferences prefs = getSharedPreferences( - getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); - prefs.edit() - .putString(KeymanSettingsActivity.spacebarTextKey, values[which].toString()) - .apply(); + // Persist the selection just like KeymanSettingsFragment does + SharedPreferences prefs = getSharedPreferences( + getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); + prefs.edit() + .putString(KeymanSettingsActivity.spacebarTextKey, values[which].toString()) + .apply(); - dialog.dismiss(); - }) - .setNegativeButton(getString(R.string.label_cancel), null) - .show(); - // } else if (id == R.id.nav_settings) { - // drawerLayout.closeDrawers(); - // startActivity(new Intent(context, KeymanSettingsActivity.class)); - } else if (id == R.id.nav_help) { + dialog.dismiss(); + }) + .setNegativeButton(getString(R.string.label_cancel), null) + .show(); +// } else if (id == R.id.nav_settings) { +// drawerLayout.closeDrawers(); +// startActivity(new Intent(context, KeymanSettingsActivity.class)); + } else if (id == R.id.nav_help + ) { drawerLayout.closeDrawers(); startActivity(new Intent(context, InfoActivity.class)); - } else if (id == R.id.nav_palette) { + }else if (id == R.id.nav_palette){ drawerLayout.closeDrawers(); openThemeDialog(); - } else if (id == R.id.nav_about_current_keyboard || id == R.id.nav_help_active_keyboard) { + } else if (id == R.id.nav_about_current_keyboard) { drawerLayout.closeDrawers(); showCurrentKeyboardSettings(); } @@ -399,7 +384,6 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } }); updateCurrentKeyboardDrawerItemTitle(navigationView); - updateInstalledLanguagesDrawerTitle(navigationView); initializeDrawerItemSubtitles(navigationView); initializeDrawerToggleOptions(navigationView); initializeDrawerCheckboxOptions(navigationView); @@ -412,19 +396,16 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene KMManager.hideSystemKeyboard(); textView.dismissKeyboard(); } - @Override public void onDrawerOpened(View drawerView) { if (navigationView != null) { updateCurrentKeyboardDrawerItemTitle(navigationView); - updateInstalledLanguagesDrawerTitle(navigationView); - initializeDrawerItemSubtitles(navigationView); } } @Override public void onDrawerClosed(View drawerView) { - textView.callOnClick(); + textView.callOnClick(); } }); @@ -450,38 +431,25 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } private void openThemeDialog() { - String[] options = { - getString(R.string.theme_dark_mode), - getString(R.string.theme_light_mode), - getString(R.string.theme_system_default) - }; + String[] options = {"Dark Mode", "Light Mode", "System Default"}; SharedPreferences themePrefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE); int currentMode = themePrefs.getInt(KEY_THEME_MODE, AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM); int checkedItem; - if (currentMode == AppCompatDelegate.MODE_NIGHT_YES) - checkedItem = 0; - else if (currentMode == AppCompatDelegate.MODE_NIGHT_NO) - checkedItem = 1; - else - checkedItem = 2; + if (currentMode == AppCompatDelegate.MODE_NIGHT_YES) checkedItem = 0; + else if (currentMode == AppCompatDelegate.MODE_NIGHT_NO) checkedItem = 1; + else checkedItem = 2; AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(getString(R.string.theme_choose)); + builder.setTitle("Choose Theme"); builder.setSingleChoiceItems(options, checkedItem, (dialog, which) -> { int modeToApply; switch (which) { - case 0: - modeToApply = AppCompatDelegate.MODE_NIGHT_YES; - break; - case 1: - modeToApply = AppCompatDelegate.MODE_NIGHT_NO; - break; - default: - modeToApply = AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM; - break; + case 0: modeToApply = AppCompatDelegate.MODE_NIGHT_YES; break; + case 1: modeToApply = AppCompatDelegate.MODE_NIGHT_NO; break; + default: modeToApply = AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM; break; } // Save the choice themePrefs.edit().putInt(KEY_THEME_MODE, modeToApply).apply(); @@ -489,13 +457,10 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene // Apply immediately AppCompatDelegate.setDefaultNightMode(modeToApply); getDelegate().applyDayNight(); // ensures current activity updates - if (navigationView != null) { - updateThemeDrawerSubtitle(navigationView); - } dialog.dismiss(); }); - builder.setNegativeButton(android.R.string.cancel, (dialog, which) -> dialog.dismiss()); + builder.setNegativeButton("Cancel", (dialog, which) -> dialog.dismiss()); builder.show(); } @@ -523,13 +488,13 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene inputStream.close(); } else if (FileUtils.hasJavaScriptExtension(filename) || FileUtils.hasFontExtension(filename)) { File packagesDir = new File(getDir("data", Context.MODE_PRIVATE) + File.separator + - KMManager.KMDefault_AssetPackages); + KMManager.KMDefault_AssetPackages); if (!packagesDir.exists()) { packagesDir.mkdir(); } File undefinedPackageDir = new File(getDir("data", Context.MODE_PRIVATE) + - File.separator + KMManager.KMDefault_UndefinedPackageID); + File.separator + KMManager.KMDefault_UndefinedPackageID); if (!undefinedPackageDir.exists()) { undefinedPackageDir.mkdir(); } @@ -552,9 +517,6 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene if (navigationView != null) { refreshDrawerSystemKeyboardCheckboxes(navigationView); - updateCurrentKeyboardDrawerItemTitle(navigationView); - updateInstalledLanguagesDrawerTitle(navigationView); - initializeDrawerItemSubtitles(navigationView); } if (textView != null) { @@ -568,11 +530,9 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene // Reset keyboard picker Activity Task flag KMManager.closeParentAppOnShowKeyboardPicker(); - // onConfigurationChanged() only triggers when device is rotated while app is in - // foreground + // onConfigurationChanged() only triggers when device is rotated while app is in foreground // This handles when device is rotated while app is in background - // using KMManager.getOrientation() since getConfiguration().orientation is - // unreliable #10241 + // using KMManager.getOrientation() since getConfiguration().orientation is unreliable #10241 int newOrientation = KMManager.getOrientation(context); if (newOrientation != lastOrientation) { lastOrientation = newOrientation; @@ -606,8 +566,8 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene CheckPermissions.requestPermission(this, context); } break; - case "http": - case "https": + case "http" : + case "https" : // Might need to modify link like KMPBrowserActivity if (KMPLink.isKeymanInstallLink(link)) { loadingIntentUri = KMPLink.getKeyboardDownloadLink(link); @@ -616,18 +576,17 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene KMLog.LogBreadcrumb(TAG, "Installing via HTTPS intent: " + link, true); downloadKMP(loadingIntentUri, KmpInstallMode.Full); break; - case "keyman": - // TODO: Only accept download links from Keyman browser activities when - // universal links work + case "keyman" : + // TODO: Only accept download links from Keyman browser activities when universal links work KMLog.LogBreadcrumb(TAG, "Installing via keyman-link intent: " + link, true); if (KMPLink.isKeymanDownloadLink(loadingIntentUri.toString())) { // Convert opaque URI to hierarchical URI so the query parameters can be parsed Builder builder = new Uri.Builder(); builder.scheme("https") - .authority("keyman.com") - .appendPath("keyboards") - .encodedQuery(loadingIntentUri.getEncodedQuery()); + .authority("keyman.com") + .appendPath("keyboards") + .encodedQuery(loadingIntentUri.getEncodedQuery()); loadingIntentUri = Uri.parse(builder.build().toString()); downloadKMP(loadingIntentUri, KmpInstallMode.Full); } else if (KMPLink.isLegacyKeymanDownloadLink(loadingIntentUri.toString())) { @@ -638,7 +597,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene KMLog.LogError(TAG, msg); } break; - default: + default : String msg = "Unrecognized scheme: " + scheme; KMLog.LogError(TAG, msg); } @@ -679,26 +638,26 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } @SuppressLint("RestrictedApi") - // @Override - // public boolean onPrepareOptionsMenu(final Menu menu) { - // this.menu = menu; - // final MenuItem _overflowMenuItem = menu.findItem(R.id.action_overflow); - // if (_overflowMenuItem != null) { - // MenuItem updateKeyboards = this.menu.findItem(R.id.action_update_keyboards); - // updateUpdateCountIndicator(updateKeyboards, - // KMManager.getUpdateTool().getOpenUpdateCount(), true); - // } - // return super.onPrepareOptionsMenu(menu); - // } +// @Override +// public boolean onPrepareOptionsMenu(final Menu menu) { +// this.menu = menu; +// final MenuItem _overflowMenuItem = menu.findItem(R.id.action_overflow); +// if (_overflowMenuItem != null) { +// MenuItem updateKeyboards = this.menu.findItem(R.id.action_update_keyboards); +// updateUpdateCountIndicator(updateKeyboards, +// KMManager.getUpdateTool().getOpenUpdateCount(), true); +// } +// return super.onPrepareOptionsMenu(menu); +// } private void updateUpdateCountIndicator(int anUpdateCount) { if (menu == null) { return; } - // final MenuItem _overflowMenuItem = menu.findItem(R.id.action_overflow); - // if (_overflowMenuItem != null) { - // updateUpdateCountIndicator(_overflowMenuItem, anUpdateCount, false); - // } +// final MenuItem _overflowMenuItem = menu.findItem(R.id.action_overflow); +// if (_overflowMenuItem != null) { +// updateUpdateCountIndicator(_overflowMenuItem, anUpdateCount, false); +// } final MenuItem _keyboardupdate = menu.findItem(R.id.action_update_keyboards); if (_keyboardupdate != null && anUpdateCount > 0) { @@ -706,24 +665,25 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } } - private void updateUpdateCountIndicator(MenuItem theItem, int anUpdateCount, boolean aHideMenuitem) { + private void updateUpdateCountIndicator(MenuItem theItem, int anUpdateCount, boolean aHideMenuitem) + { final ViewGroup _rootView = (ViewGroup) theItem.getActionView(); - if (anUpdateCount == 0) { + if(anUpdateCount==0) { if (aHideMenuitem) { theItem.setVisible(false); } else if (_rootView != null) { _rootView.findViewById(R.id.update_count_indicator).setVisibility(View.GONE); } } else { - if (aHideMenuitem) { + if(aHideMenuitem) { theItem.setVisible(true); - } else if (_rootView != null) { + } else if(_rootView!=null) { _rootView.findViewById(R.id.update_count_indicator).setVisibility(View.VISIBLE); } } - if (_rootView == null) { + if(_rootView==null) { return; } @@ -737,39 +697,39 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene getMenuInflater().inflate(R.menu.main, menu); this.menu = menu; - KMManager.getUpdateTool().addPropertyChangeListener(new PropertyChangeListener() { - @Override - public void propertyChange(PropertyChangeEvent evt) { - if (!evt.getPropertyName().equals("updateCount")) - return; - updateUpdateCountIndicator( - evt.getNewValue() == null ? 0 : (Integer) evt.getNewValue()); + KMManager.getUpdateTool().addPropertyChangeListener(new PropertyChangeListener() + { + @Override + public void propertyChange(PropertyChangeEvent evt) { + if(!evt.getPropertyName().equals("updateCount")) + return; + updateUpdateCountIndicator( + evt.getNewValue()==null?0:(Integer) evt.getNewValue()); + } } - }); + ); updateUpdateCountIndicator(KMManager.getUpdateTool().getOpenUpdateCount()); return true; } + @Override public boolean onOptionsItemSelected(MenuItem item) { - // Android Gradle 8.0 no longer declares resources final, so can't use switch - // statement here + // Android Gradle 8.0 no longer declares resources final, so can't use switch statement here if (item.getItemId() == R.id.action_info) { showInfo(); return true; } else if (item.getItemId() == R.id.action_share) { showShareDialog(); return true; - /* - * Disable Web Browser to investigate Google sign-in - * } else if ((item.getItemId() == R.id.action_web) { - * showWebBrowser(); - * return true; - */ + /* Disable Web Browser to investigate Google sign-in + } else if ((item.getItemId() == R.id.action_web) { + showWebBrowser(); + return true;*/ } else if (item.getItemId() == R.id.action_text_size) { showTextSizeDialog(); return true; - } else if (item.getItemId() == R.id.action_clear_text) { + } else if (item.getItemId() == R.id.action_clear_text){ showClearTextDialog(); return true; } else if (item.getItemId() == R.id.action_get_started) { @@ -883,7 +843,6 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene keyboardToolbarContainer = findViewById(R.id.keyboardToolbarContainer); if (keyboardToolbarToggleButton == null || keyboardToolbarContainer == null) { - reportKeyboardToolbarLayoutIssue("Keyboard toolbar layout is missing required views."); return; } @@ -905,8 +864,6 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene showShareDialog(); setKeyboardToolbarExpanded(false, true); }); - } else { - reportKeyboardToolbarLayoutIssue("Keyboard toolbar share button is missing."); } View textSizeButton = findViewById(R.id.keyboardToolbarTextSizeButton); @@ -915,8 +872,6 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene showTextSizeDialog(); setKeyboardToolbarExpanded(false, true); }); - } else { - reportKeyboardToolbarLayoutIssue("Keyboard toolbar text size button is missing."); } View clearButton = findViewById(R.id.keyboardToolbarClearButton); @@ -925,15 +880,6 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene showClearTextDialog(); setKeyboardToolbarExpanded(false, true); }); - } else { - reportKeyboardToolbarLayoutIssue("Keyboard toolbar clear button is missing."); - } - } - - private void reportKeyboardToolbarLayoutIssue(String message) { - KMLog.LogError(TAG, message); - if (Sentry.isEnabled()) { - Sentry.captureMessage(message); } } @@ -945,7 +891,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene isKeyboardToolbarExpanded = expanded; keyboardToolbarContainer.setVisibility(expanded ? View.VISIBLE : View.GONE); - float targetRotation = expanded ? 0f : 180f; + float targetRotation = expanded ? 180f : 0f; if (animateRotation) { keyboardToolbarToggleButton.animate().rotation(targetRotation).setDuration(180).start(); } else { @@ -954,11 +900,9 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } private void updateKeyboardToolbarPosition() { - int keyboardTopOffset = KMManager.getKeyboardHeight(this) + KMManager.getBannerHeight(this) - + getResources().getDimensionPixelSize(R.dimen.keyboard_toolbar_keyboard_gap); + int keyboardTopOffset = KMManager.getKeyboardHeight(this) + KMManager.getBannerHeight(this) + dpToPx(12); updateBottomMargin(keyboardToolbarToggleButton, keyboardTopOffset); - updateBottomMargin(keyboardToolbarContainer, - getResources().getDimensionPixelSize(R.dimen.keyboard_toolbar_container_bottom_margin)); + updateBottomMargin(keyboardToolbarContainer, dpToPx(8)); } private void updateBottomMargin(View view, int bottomMargin) { @@ -978,40 +922,39 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } } + private int dpToPx(int dp) { + float density = getResources().getDisplayMetrics().density; + return Math.round(dp * density); + } + public void downloadKMP(String packageId, String bcp47, KmpInstallMode installMode) { - Uri downloadUri = bcp47 == null - ? Uri.parse(KMString.format("https://keyman.com/go/package/download/%s", new Object[] { packageId })) - : Uri.parse( - KMString.format("https://keyman.com/go/package/download/%s?bcp47=%s", new Object[] { packageId, bcp47 })); + Uri downloadUri = bcp47 == null ? + Uri.parse(KMString.format("https://keyman.com/go/package/download/%s", new Object[]{packageId})) : + Uri.parse(KMString.format("https://keyman.com/go/package/download/%s?bcp47=%s", new Object[]{packageId, bcp47})); downloadKMP(downloadUri, installMode); } /** - * Parse the URI data to determine the filename and URL for the .kmp keyboard - * package. + * Parse the URI data to determine the filename and URL for the .kmp keyboard package. * If URL is valid, download the kmp. - * - * @param packageUri URI to download the package. + * @param packageUri URI to download the package. * @param installMode KMP installation mode (silent, welcome only, or full) - * TODO: only ever pass packageId and bcp47 from callers, as - * KMPLink should be responsible for - * URL parsing, not this function. + * TODO: only ever pass packageId and bcp47 from callers, as KMPLink should be responsible for + * URL parsing, not this function. */ public void downloadKMP(Uri packageUri, KmpInstallMode installMode) { if (packageUri == null) { - KMLog.LogError(TAG, "null uri passed to downloadKmp"); + KMLog.LogError(TAG,"null uri passed to downloadKmp"); String message = "Download failed. Invalid URL."; Toast.makeText(getApplicationContext(), message, - Toast.LENGTH_SHORT).show(); + Toast.LENGTH_SHORT).show(); return; } try { - // Initial try with Keyman 13.0 download link, format - // keyman://localhost/install?url=... + // Initial try with Keyman 13.0 download link, format keyman://localhost/install?url=... String url = packageUri.getQueryParameter(KMKeyboardDownloaderActivity.KMKey_URL); if (url == null) { - // Otherwise, Keyman 14+ download link is format - // https://keyman.com/go/package/download/[?bcp47=][&...] + // Otherwise, Keyman 14+ download link is format https://keyman.com/go/package/download/[?bcp47=][&...] url = packageUri.toString(); } if (url != null) { @@ -1027,8 +970,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene // Parse query for the BCP 47 language ID String languageID = packageUri.getQueryParameter(KMKeyboardDownloaderActivity.KMKey_BCP47); - // TODO: Using "tag" for now, but production will be - // KMKeyboardDownloaderActivity.KMKey_BCP47 + // TODO: Using "tag" for now, but production will be KMKeyboardDownloaderActivity.KMKey_BCP47 if (languageID == null) { languageID = packageUri.getQueryParameter("tag"); } @@ -1062,18 +1004,18 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } catch (Exception e) { KMLog.LogException(TAG, "", e); cleanupPackageInstall(); - return;// break; + return;//break; } } else { String message = "Download failed. Not a .kmp keyboard package."; Toast.makeText(getApplicationContext(), message, - Toast.LENGTH_SHORT).show(); + Toast.LENGTH_SHORT).show(); } } catch (UnsupportedOperationException e) { String message = "Download failed. Invalid URL."; - KMLog.LogException(TAG, message, e); + KMLog.LogException(TAG, message, e); Toast.makeText(getApplicationContext(), message, - Toast.LENGTH_SHORT).show(); + Toast.LENGTH_SHORT).show(); cleanupPackageInstall(); } } @@ -1093,8 +1035,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene // Status bar height only used for portrait orientation int statusBarHeight = 0; if (lastOrientation == Configuration.ORIENTATION_PORTRAIT) { - // *** TO DO: Try to check if status bar is visible, set statusBarHeight to 0 if - // it is not visible *** + // *** TO DO: Try to check if status bar is visible, set statusBarHeight to 0 if it is not visible *** int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android"); if (resourceId > 0) { statusBarHeight = getResources().getDimensionPixelSize(resourceId); @@ -1105,7 +1046,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene Point size = KMManager.getWindowSize(context); int screenHeight = size.y; textView.setHeight( - screenHeight - statusBarHeight - actionBarHeight - bannerHeight - keyboardHeight - navigationBarHeight); + screenHeight - statusBarHeight - actionBarHeight - bannerHeight - keyboardHeight - navigationBarHeight); } private void showInfo() { @@ -1128,10 +1069,8 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene private void showShareDialog() { // Sharing text content via send intent - // Reference: - // https://developer.android.com/training/sharing/send#send-text-content - // Note: Sharing to Facebook removed in - // https://github.com/keymanapp/keyman/issues/156 + // Reference: https://developer.android.com/training/sharing/send#send-text-content + // Note: Sharing to Facebook removed in https://github.com/keymanapp/keyman/issues/156 // Users can copy text or use Keyman as system keyboard for typing into Facebook Intent sendIntent = new Intent(Intent.ACTION_SEND); sendIntent.setType("text/plain"); @@ -1206,12 +1145,10 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene /** * Combine a localized string for "Text Size" plus Arabic numerals - * * @return String */ private String getTextSizeString() { - // Instead of formatting the number, will truncate formatting and concat the - // actual textSize + // Instead of formatting the number, will truncate formatting and concat the actual textSize String label = getString(R.string.text_size).replace("%1$d", ""); return label + KMString.format(" %d", textSize); } @@ -1235,7 +1172,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } }); - // dialogBuilder.show(); no dialog + //dialogBuilder.show(); no dialog final AlertDialog dialog = dialogBuilder.create(); dialog.show(); dialog.getWindow().setBackgroundDrawableResource(R.color.dialogBackground); @@ -1276,7 +1213,6 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene /** * Based on whether Chrome and WebView are installed & enabled, * display the corresponding update panel - * * @param chromeStatus * @param webViewStatus */ @@ -1296,10 +1232,10 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } private void displayInstallWebView() { - TextView textView = (TextView) findViewById((R.id.kmWebViewChromeTextView)); + TextView textView = (TextView)findViewById((R.id.kmWebViewChromeTextView)); textView.setText(getString(R.string.body_install_webview)); - Button button = (Button) findViewById(R.id.webViewChromeButton); + Button button = (Button)findViewById(R.id.webViewChromeButton); button.setText(getString(R.string.label_install_webview)); button.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { @@ -1314,8 +1250,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene startActivity(intent); } } catch (android.content.ActivityNotFoundException e) { - Toast.makeText(getApplicationContext(), getString(R.string.unable_to_open_browser), Toast.LENGTH_SHORT) - .show(); + Toast.makeText(getApplicationContext(), getString(R.string.unable_to_open_browser), Toast.LENGTH_SHORT).show(); } } }); @@ -1325,16 +1260,16 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } private void displayEnableWebView() { - TextView textView = (TextView) findViewById((R.id.kmWebViewChromeTextView)); + TextView textView = (TextView)findViewById((R.id.kmWebViewChromeTextView)); textView.setText(getString(R.string.body_enable_webview)); - Button button = (Button) findViewById(R.id.webViewChromeButton); + Button button = (Button)findViewById(R.id.webViewChromeButton); button.setText(getString(R.string.label_enable_webview)); button.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { // Application settings to enable WebView Intent intent = new Intent("android.settings.APPLICATION_DETAILS_SETTINGS", - Uri.parse("package:com.google.android.webview")); + Uri.parse("package:com.google.android.webview")); // Launch intent try { @@ -1343,8 +1278,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene startActivity(intent); } } catch (android.content.ActivityNotFoundException e) { - Toast.makeText(getApplicationContext(), getString(R.string.unable_to_open_browser), Toast.LENGTH_SHORT) - .show(); + Toast.makeText(getApplicationContext(), getString(R.string.unable_to_open_browser), Toast.LENGTH_SHORT).show(); } } }); @@ -1355,17 +1289,17 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene private void displayUpdateChrome() { // TextView's default string is to update Chrome - TextView textView = (TextView) findViewById(R.id.kmWebViewChromeTextView); + TextView textView = (TextView)findViewById(R.id.kmWebViewChromeTextView); textView.setText(String.format(getString(R.string.text_require_chrome_version), - WebViewUtils.KEYMAN_MIN_TARGET_VERSION_ANDROID_CHROME)); + WebViewUtils.KEYMAN_MIN_TARGET_VERSION_ANDROID_CHROME)); - Button button = (Button) findViewById(R.id.webViewChromeButton); + Button button = (Button)findViewById(R.id.webViewChromeButton); button.setText(getString(R.string.button_update_chrome)); button.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { // Primary Play Store link to install/update Chrome Intent intent = new Intent(Intent.ACTION_VIEW, - Uri.parse("market://details?id=com.android.chrome")); + Uri.parse("market://details?id=com.android.chrome")); try { PackageManager packageManager = context.getPackageManager(); @@ -1374,14 +1308,13 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } else { // (alternate Play Store link to install/update Chrome) intent = new Intent(Intent.ACTION_VIEW, - Uri.parse("https://play.google.com/store/apps/details?id=com.android.chrome")); + Uri.parse("https://play.google.com/store/apps/details?id=com.android.chrome")); intent.resolveActivity(packageManager); startActivity(intent); } } catch (android.content.ActivityNotFoundException e) { - Toast.makeText(getApplicationContext(), getString(R.string.unable_to_open_browser), Toast.LENGTH_SHORT) - .show(); + Toast.makeText(getApplicationContext(), getString(R.string.unable_to_open_browser), Toast.LENGTH_SHORT).show(); } } }); @@ -1428,8 +1361,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene return; } else if (!isKMP) { String noKeyboardsInstalledMessage = String.format( - context.getString(R.string.not_valid_package_file), filename, - context.getString(R.string.no_targets_to_install)); + context.getString(R.string.not_valid_package_file), filename, context.getString(R.string.no_targets_to_install)); Toast.makeText(context, noKeyboardsInstalledMessage, Toast.LENGTH_LONG).show(); return; } @@ -1459,52 +1391,69 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene SharedPreferences prefs = getSharedPreferences(getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); bindDrawerSwitch(navigationView, R.id.nav_toggle_show_osk, - prefs.getBoolean(KeymanSettingsActivity.oskWithPhysicalKeyboardKey, false), - new SwitchChangeHandler() { - @Override - public void onChanged(boolean isChecked) { - SharedPreferences.Editor editor = prefs.edit(); - editor.putBoolean(KeymanSettingsActivity.oskWithPhysicalKeyboardKey, isChecked); - editor.apply(); - } - }); + prefs.getBoolean(KeymanSettingsActivity.oskWithPhysicalKeyboardKey, false), + new SwitchChangeHandler() { + @Override + public void onChanged(boolean isChecked) { + SharedPreferences.Editor editor = prefs.edit(); + editor.putBoolean(KeymanSettingsActivity.oskWithPhysicalKeyboardKey, isChecked); + editor.apply(); + } + }); bindDrawerSwitch(navigationView, R.id.action_get_started, - prefs.getBoolean(GetStartedActivity.showGetStartedKey, true), - new SwitchChangeHandler() { - @Override - public void onChanged(boolean isChecked) { - SharedPreferences.Editor editor = prefs.edit(); - editor.putBoolean(GetStartedActivity.showGetStartedKey, isChecked); - editor.apply(); - } - }); + prefs.getBoolean(GetStartedActivity.showGetStartedKey, true), + new SwitchChangeHandler() { + @Override + public void onChanged(boolean isChecked) { + SharedPreferences.Editor editor = prefs.edit(); + editor.putBoolean(GetStartedActivity.showGetStartedKey, isChecked); + editor.apply(); + } + }); bindDrawerSwitch(navigationView, R.id.nav_toggle_send_crash_report, - prefs.getBoolean(KeymanSettingsActivity.sendCrashReport, true), - new SwitchChangeHandler() { - @Override - public void onChanged(boolean isChecked) { - SharedPreferences.Editor editor = prefs.edit(); - editor.putBoolean(KeymanSettingsActivity.sendCrashReport, isChecked); - editor.apply(); - KMManager.setMaySendCrashReport(isChecked); - } - }); + prefs.getBoolean(KeymanSettingsActivity.sendCrashReport, true), + new SwitchChangeHandler() { + @Override + public void onChanged(boolean isChecked) { + SharedPreferences.Editor editor = prefs.edit(); + editor.putBoolean(KeymanSettingsActivity.sendCrashReport, isChecked); + editor.apply(); + KMManager.setMaySendCrashReport(isChecked); + } + }); } private void initializeDrawerItemSubtitles(NavigationView navigationView) { +// setDrawerItemSubtitle(navigationView, R.id.nav_installed_languages, +// getString(R.string.drawer_subtitle_installed_languages)); +// setDrawerItemSubtitle(navigationView, R.id.nav_install_keyboard, +// getString(R.string.drawer_subtitle_install_keyboard)); +// setDrawerItemSubtitle(navigationView, R.id.nav_checkbox_enable_system_keyboard, +// getString(R.string.drawer_subtitle_enable_system_keyboard)); +// setDrawerItemSubtitle(navigationView, R.id.nav_checkbox_set_default_keyboard, +// getString(R.string.drawer_subtitle_set_default_keyboard)); +// setDrawerItemSubtitle(navigationView, R.id.nav_display_language, +// getString(R.string.drawer_subtitle_display_language)); +// setDrawerItemSubtitle(navigationView, R.id.nav_keyboard_height, +// getString(R.string.drawer_subtitle_keyboard_height)); +// setDrawerItemSubtitle(navigationView, R.id.nav_longpress_delay, +// getString(R.string.drawer_subtitle_longpress_delay)); setDrawerItemSubtitle(navigationView, R.id.nav_spacebar_caption, - getString(R.string.drawer_subtitle_spacebar_caption)); + getString(R.string.drawer_subtitle_spacebar_caption)); setDrawerItemSubtitle(navigationView, R.id.nav_toggle_show_osk, - getString(R.string.drawer_subtitle_show_osk)); + getString(R.string.drawer_subtitle_show_osk)); +// setDrawerItemSubtitle(navigationView, R.id.nav_toggle_haptic_feedback, +// getString(R.string.drawer_subtitle_haptic_feedback)); setDrawerItemSubtitle(navigationView, R.id.nav_toggle_send_crash_report, - getString(R.string.drawer_subtitle_send_crash_report)); + getString(R.string.drawer_subtitle_send_crash_report)); +// setDrawerItemSubtitle(navigationView, R.id.nav_settings, +// getString(R.string.drawer_subtitle_more_settings)); + setDrawerItemSubtitle(navigationView, R.id.nav_help, + getString(R.string.drawer_subtitle_about)); setDrawerItemSubtitle(navigationView, R.id.nav_about_current_keyboard, getString(R.string.drawer_subtitle_about_current_keyboard)); - updateThemeDrawerSubtitle(navigationView); - updateCurrentKeyboardDrawerSubtitle(navigationView); - updateActiveKeyboardHelpDrawerItem(navigationView); } private void updateCurrentKeyboardDrawerItemTitle(NavigationView navigationView) { @@ -1513,7 +1462,13 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene return; } - String newTitle = getString(R.string.drawer_about_current_keyboard); + Keyboard currentKeyboard = getBestAvailableKeyboard(); + String newTitle; + if (currentKeyboard != null && currentKeyboard.getKeyboardName() != null && !currentKeyboard.getKeyboardName().isEmpty()) { + newTitle = getString(R.string.drawer_about_current_keyboard) + ": " + currentKeyboard.getKeyboardName(); + } else { + newTitle = getString(R.string.drawer_about_current_keyboard) + ": " + getString(R.string.drawer_about_no_active_keyboard); + } if (!newTitle.equals(lastCurrentKeyboardDrawerTitle)) { menuItem.setTitle(newTitle); @@ -1521,84 +1476,6 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } } - private void updateCurrentKeyboardDrawerSubtitle(NavigationView navigationView) { - setDrawerItemSubtitle(navigationView, R.id.nav_about_current_keyboard, getCurrentKeyboardDisplayName()); - } - - private void updateThemeDrawerSubtitle(NavigationView navigationView) { - setDrawerItemSubtitle( - navigationView, - R.id.nav_palette, - getString(R.string.drawer_subtitle_theme, getThemeModeLabel()) - ); - } - - private void updateActiveKeyboardHelpDrawerItem(NavigationView navigationView) { - MenuItem menuItem = navigationView.getMenu().findItem(R.id.nav_help_active_keyboard); - if (menuItem == null) { - return; - } - - String keyboardName = getCurrentKeyboardDisplayName(); - if (keyboardName.equals(getString(R.string.drawer_about_no_active_keyboard))) { - menuItem.setTitle(getString(R.string.drawer_title_active_keyboard_help_fallback)); - setDrawerItemSubtitle(navigationView, R.id.nav_help_active_keyboard, keyboardName); - return; - } - - menuItem.setTitle(getString(R.string.drawer_title_active_keyboard_help, keyboardName)); - setDrawerItemSubtitle( - navigationView, - R.id.nav_help_active_keyboard, - getString(R.string.drawer_subtitle_active_keyboard_help, keyboardName) - ); - } - - private void updateInstalledLanguagesDrawerTitle(NavigationView navigationView) { - MenuItem menuItem = navigationView.getMenu().findItem(R.id.nav_installed_languages); - if (menuItem == null) { - return; - } - - menuItem.setTitle(getInstalledLanguagesText()); - } - - private String getInstalledLanguagesText() { - Dataset installedDataset = KMManager.getInstalledDataset(context); - if (installedDataset == null) { - return getResources().getQuantityString(R.plurals.installed_languages, 0, 0); - } - - int langCount = 0; - for (Dataset.LanguageDataset language : installedDataset.asList()) { - if (language.keyboards.size() > 0) { - langCount++; - } - } - return getResources().getQuantityString(R.plurals.installed_languages, langCount, langCount); - } - - private String getCurrentKeyboardDisplayName() { - Keyboard currentKeyboard = getBestAvailableKeyboard(); - if (currentKeyboard != null && currentKeyboard.getKeyboardName() != null - && !currentKeyboard.getKeyboardName().isEmpty()) { - return currentKeyboard.getKeyboardName(); - } - return getString(R.string.drawer_about_no_active_keyboard); - } - - private String getThemeModeLabel() { - SharedPreferences themePrefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE); - int currentMode = themePrefs.getInt(KEY_THEME_MODE, AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM); - if (currentMode == AppCompatDelegate.MODE_NIGHT_YES) { - return getString(R.string.theme_dark_mode); - } - if (currentMode == AppCompatDelegate.MODE_NIGHT_NO) { - return getString(R.string.theme_light_mode); - } - return getString(R.string.theme_system_default); - } - private void showCurrentKeyboardSettings() { Keyboard currentKeyboard = getBestAvailableKeyboard(); if (currentKeyboard == null) { @@ -1669,47 +1546,49 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene SpannableString fullTitle = new SpannableString(baseTitle + "\n" + subtitle); int subtitleStart = baseTitle.length() + 1; fullTitle.setSpan(new RelativeSizeSpan(0.85f), subtitleStart, fullTitle.length(), - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); TypedValue typedValue = new TypedValue(); getTheme().resolveAttribute(android.R.attr.textColorSecondary, typedValue, true); int subtitleColor = typedValue.resourceId != 0 - ? ContextCompat.getColor(this, typedValue.resourceId) - : typedValue.data; + ? ContextCompat.getColor(this, typedValue.resourceId) + : typedValue.data; fullTitle.setSpan(new ForegroundColorSpan(subtitleColor), subtitleStart, fullTitle.length(), - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); menuItem.setTitle(fullTitle); } private void initializeDrawerCheckboxOptions(NavigationView navigationView) { bindDrawerCheckboxAction(navigationView, R.id.nav_checkbox_enable_system_keyboard, - () -> { - if (drawerLayout != null) { - drawerLayout.closeDrawers(); - } + new Runnable() { + @Override + public void run() { startActivity(new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS)); - }); + } + }); bindDrawerCheckboxAction(navigationView, R.id.nav_checkbox_set_default_keyboard, - this::showDefaultKeyboardPickerFromMainPage); - } - - private void showDefaultKeyboardPickerFromMainPage() { - Intent pickerIntent = new Intent(this, GetStartedActivity.class); - pickerIntent.putExtra(GetStartedActivity.openDefaultKeyboardPickerOnlyKey, true); - startActivity(pickerIntent); + new Runnable() { + @Override + public void run() { + InputMethodManager imManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); + if (imManager != null) { + imManager.showInputMethodPicker(); + } + } + }); } private void refreshDrawerSystemKeyboardCheckboxes(NavigationView navigationView) { setDrawerCheckboxState(navigationView, R.id.nav_checkbox_enable_system_keyboard, - SystemIMESettings.isEnabledAsSystemKB(context)); + SystemIMESettings.isEnabledAsSystemKB(context)); setDrawerCheckboxState(navigationView, R.id.nav_checkbox_set_default_keyboard, - SystemIMESettings.isDefaultKB(context)); + SystemIMESettings.isDefaultKB(context)); } private void bindDrawerSwitch(NavigationView navigationView, int menuItemId, boolean defaultValue, - final SwitchChangeHandler switchChangeHandler) { + final SwitchChangeHandler switchChangeHandler) { MenuItem menuItem = navigationView.getMenu().findItem(menuItemId); if (menuItem == null) { return; @@ -1763,8 +1642,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } } - private void bindDrawerCheckboxAction(NavigationView navigationView, int menuItemId, - final Runnable onActivate) { + private void bindDrawerCheckboxAction(NavigationView navigationView, int menuItemId, final Runnable onActivate) { MenuItem menuItem = navigationView.getMenu().findItem(menuItemId); if (menuItem == null) { return; @@ -1788,18 +1666,8 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene return; } - checkBox.setClickable(true); - checkBox.setFocusable(false); - checkBox.setOnClickListener(v -> { - if (onActivate != null) { - onActivate.run(); - } - }); - actionView.setOnClickListener(v -> { - if (onActivate != null) { - onActivate.run(); - } - }); + checkBox.setOnClickListener(v -> onActivate.run()); + actionView.setOnClickListener(v -> onActivate.run()); } private void setDrawerCheckboxState(NavigationView navigationView, int menuItemId, boolean isChecked) { @@ -1835,8 +1703,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene public static void cleanupPackageInstall() { if (progressDialog != null && progressDialog.isShowing()) { progressDialog.dismiss(); - } - ; + }; progressDialog = null; } @@ -1859,8 +1726,8 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene Paint p = new Paint(); p.setStyle(Paint.Style.FILL); - // p.setColor(Color.BLACK); - p.setColor(ContextCompat.getColor(context, R.color.toolbarColor)); + //p.setColor(Color.BLACK); + p.setColor(ContextCompat.getColor(context,R.color.toolbarColor)); canvas.drawRect(new Rect(0, 0, width, actionBarHeight), p); p.setColor(context.getResources().getColor(R.color.keyman_orange)); @@ -1895,21 +1762,21 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene HashMap hashMap = new HashMap<>(keyboardsInstalled.get(i)); String languageID = hashMap.get(KMManager.KMKey_LanguageID); Keyboard keyboardInfo = new Keyboard( - hashMap.get(KMManager.KMKey_PackageID), - hashMap.get(KMManager.KMKey_KeyboardID), - hashMap.get(KMManager.KMKey_KeyboardName), - languageID, - hashMap.get(KMManager.KMKey_LanguageName), - hashMap.get(KMManager.KMKey_Version), - hashMap.get(KMManager.KMKey_CustomHelpLink), - hashMap.get(KMManager.KMKey_KMPLink), - true, - hashMap.get(KMManager.KMKey_Font), - hashMap.get(KMManager.KMKey_OskFont)); + hashMap.get(KMManager.KMKey_PackageID), + hashMap.get(KMManager.KMKey_KeyboardID), + hashMap.get(KMManager.KMKey_KeyboardName), + languageID, + hashMap.get(KMManager.KMKey_LanguageName), + hashMap.get(KMManager.KMKey_Version), + hashMap.get(KMManager.KMKey_CustomHelpLink), + hashMap.get(KMManager.KMKey_KMPLink), + true, + hashMap.get(KMManager.KMKey_Font), + hashMap.get(KMManager.KMKey_OskFont)); - if (hashMap.containsKey(KMManager.KMKey_KMPInstall_Mode)) { - installMode = KmpInstallMode.fromString(hashMap.get(KMManager.KMKey_KMPInstall_Mode)); - } + if (hashMap.containsKey(KMManager.KMKey_KMPInstall_Mode)) { + installMode = KmpInstallMode.fromString(hashMap.get(KMManager.KMKey_KMPInstall_Mode)); + } if (i == 0) { packageID = keyboardInfo.getPackageID(); customHelpLink = keyboardInfo.getHelpLink(); @@ -1937,11 +1804,11 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene ArrayList aPreparedCloudApiParams = new ArrayList<>(); String url = CloudRepository.prepareLexicalModelQuery(languageID); aPreparedCloudApiParams.add(new CloudApiTypes.CloudApiParam( - CloudApiTypes.ApiTarget.KeyboardLexicalModels, url).setType(CloudApiTypes.JSONType.Array)); + CloudApiTypes.ApiTarget.KeyboardLexicalModels, url).setType(CloudApiTypes.JSONType.Array)); CloudDownloadMgr.getInstance().executeAsDownload( - context, _downloadid, null, _callback, - aPreparedCloudApiParams.toArray(new CloudApiTypes.CloudApiParam[0])); + context, _downloadid, null, _callback, + aPreparedCloudApiParams.toArray(new CloudApiTypes.CloudApiParam[0])); } } } @@ -1963,14 +1830,14 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene public void onLexicalModelInstalled(List> lexicalModelsInstalled) { cleanupPackageInstall(); - String langId = (KMManager.getCurrentKeyboardInfo(this) != null) - ? KMManager.getCurrentKeyboardInfo(this).getLanguageID() - : KMManager.KMDefault_LanguageID; + String langId = (KMManager.getCurrentKeyboardInfo(this) != null) ? + KMManager.getCurrentKeyboardInfo(this).getLanguageID() : + KMManager.KMDefault_LanguageID; boolean matchingModel = false; - for (int i = 0; i < lexicalModelsInstalled.size(); i++) { - HashMap lexicalModelInfo = new HashMap<>(lexicalModelsInstalled.get(i)); - if (lexicalModelInfo.get(KMManager.KMKey_LanguageID).equals(langId)) { + for(int i=0; ilexicalModelInfo = new HashMap<>(lexicalModelsInstalled.get(i)); + if(lexicalModelInfo.get(KMManager.KMKey_LanguageID).equals(langId)) { matchingModel = true; } KMManager.addLexicalModel(this, lexicalModelInfo); @@ -1978,7 +1845,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene // We're on the main thread, so if the active keyboard's language code matches, // let's register the associated lexical model. - if (matchingModel) { + if(matchingModel) { KMManager.registerAssociatedLexicalModel(langId); } } diff --git a/android/KMAPro/kMAPro/src/main/res/layout/check_chrome_webview_layout.xml b/android/KMAPro/kMAPro/src/main/res/layout/check_chrome_webview_layout.xml index 396f02ac41..7ceb1bad59 100644 --- a/android/KMAPro/kMAPro/src/main/res/layout/check_chrome_webview_layout.xml +++ b/android/KMAPro/kMAPro/src/main/res/layout/check_chrome_webview_layout.xml @@ -4,7 +4,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingVertical="4dp" - android:background="@android:color/white" + android:background="?android:attr/windowBackground" android:gravity="center_vertical" android:orientation="horizontal"> @@ -14,7 +14,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" - android:background="@android:color/white" + android:background="@android:color/transparent" android:gravity="start|center_vertical" android:layout_gravity="center_vertical" android:text="@string/text_require_chrome_version" diff --git a/android/KMAPro/kMAPro/src/main/res/values/colors.xml b/android/KMAPro/kMAPro/src/main/res/values/colors.xml index ab162552d7..4f59937b80 100644 --- a/android/KMAPro/kMAPro/src/main/res/values/colors.xml +++ b/android/KMAPro/kMAPro/src/main/res/values/colors.xml @@ -35,6 +35,6 @@ #FFFFFF @color/keyman_blue - #000000 + From a65fef6c16739f6d7a2957ae1fd70480426951bf Mon Sep 17 00:00:00 2001 From: Mengchou97 Date: Tue, 12 May 2026 15:20:26 +0700 Subject: [PATCH 045/140] updated the repeated keyboard option --- android/KMAPro/kMAPro/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/KMAPro/kMAPro/src/main/res/values/strings.xml b/android/KMAPro/kMAPro/src/main/res/values/strings.xml index fe576a08d1..ce8a9cd7e5 100644 --- a/android/KMAPro/kMAPro/src/main/res/values/strings.xml +++ b/android/KMAPro/kMAPro/src/main/res/values/strings.xml @@ -25,7 +25,7 @@ About active keyboard No active keyboard Current setting: %1$s - Learn how to use the %1$s keyboard + Learn how to use the %1$s %1$s help Active keyboard help From 3a817e9abf96b39b3c3e506a047823dd2f8da576 Mon Sep 17 00:00:00 2001 From: Mengchou97 Date: Tue, 12 May 2026 17:12:04 +0700 Subject: [PATCH 046/140] refactor MainActivity and clean up outdated review comments --- .../kMAPro/src/main/AndroidManifest.xml | 13 +- .../com/keyman/android/SystemKeyboard.java | 8 +- .../kmapro/AdjustKeyboardHeightActivity.java | 19 +- .../kmapro/AdjustLongpressDelayActivity.java | 23 +- .../kmapro/KMPBrowserActivity.java | 18 +- .../kmapro/KeyboardSettingsActivity.java | 2 - .../{MyApp.java => KeymanApplication.java} | 8 +- .../kmapro/KeymanSettingsActivity.java | 59 ----- .../kmapro/KeymanSettingsFragment.java | 25 +- .../kmapro/KeymanSettingsKeys.java | 13 + .../kmapro/LanguageSettingsActivity.java | 2 - .../kmapro/LanguagesSettingsActivity.java | 1 - .../com/tavultesoft/kmapro/MainActivity.java | 237 +++++++----------- .../kmapro/SplashScreenActivity.java | 25 +- .../main/res/drawable/ic_action_decrement.xml | 1 - .../main/res/drawable/ic_action_increment.xml | 1 - .../src/main/res/drawable/ic_checkbox.xml | 21 +- .../src/main/res/drawable/ic_hamburger.xml | 22 -- .../main/res/layout/get_started_toolbar.xml | 1 - .../res/layout/keyman_settings_layout.xml | 1 - .../kMAPro/src/main/res/values/colors.xml | 1 - .../kMAPro/src/main/res/values/dimens.xml | 1 - .../kMAPro/src/main/res/values/styles.xml | 13 +- 23 files changed, 132 insertions(+), 383 deletions(-) rename android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/{MyApp.java => KeymanApplication.java} (78%) delete mode 100644 android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsActivity.java create mode 100644 android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsKeys.java delete mode 100644 android/KMAPro/kMAPro/src/main/res/drawable/ic_hamburger.xml diff --git a/android/KMAPro/kMAPro/src/main/AndroidManifest.xml b/android/KMAPro/kMAPro/src/main/AndroidManifest.xml index 4964e7b450..27488ca166 100644 --- a/android/KMAPro/kMAPro/src/main/AndroidManifest.xml +++ b/android/KMAPro/kMAPro/src/main/AndroidManifest.xml @@ -17,6 +17,7 @@ https://support.google.com/googleplay/android-developer/answer/9214102# --> - - - = Build.VERSION_CODES.M) { - Window window = getWindow(); - window.setStatusBarColor(ContextCompat.getColor(this, R.color.keyman_blue)); - boolean isDarkBackground = true; - int flags = window.getDecorView().getSystemUiVisibility(); - if (isDarkBackground) { - flags &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; - } else { - flags |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; - } - window.getDecorView().setSystemUiVisibility(flags); - WindowCompat.setDecorFitsSystemWindows(window, false); - } + setupStatusBarColors(R.color.keyman_blue, android.R.color.white); Toolbar toolbar = findViewById(R.id.titlebar); setSupportActionBar(toolbar); diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java index 8bf5d9c2a5..e0d7c9d5f5 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java @@ -8,25 +8,17 @@ package com.tavultesoft.kmapro; import android.content.Context; import android.content.SharedPreferences; import android.graphics.drawable.ColorDrawable; -import android.os.Build; import android.os.Bundle; import android.view.View; -import android.view.Window; import android.widget.SeekBar; import android.widget.TextView; import androidx.appcompat.app.ActionBar; import androidx.appcompat.widget.Toolbar; import androidx.core.content.ContextCompat; -import androidx.core.view.WindowCompat; import com.keyman.engine.BaseActivity; import com.keyman.engine.KMManager; -import android.view.Window; -import android.view.View; -import android.view.WindowManager; -import android.os.Build; -import androidx.core.content.ContextCompat; /** * Settings menu for adjusting the longpress delay time. The value for the current longpress delay time @@ -67,20 +59,7 @@ public class AdjustLongpressDelayActivity extends BaseActivity { setContentView(R.layout.activity_adjust_longpress_delay); setupEdgeToEdge(R.id.adjust_longpress_layout); -// setupStatusBarColors(R.color.keyman_blue, android.R.color.white); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - Window window = getWindow(); - window.setStatusBarColor(ContextCompat.getColor(this, R.color.keyman_blue)); - boolean isDarkBackground = true; - int flags = window.getDecorView().getSystemUiVisibility(); - if (isDarkBackground) { - flags &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; - } else { - flags |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; - } - window.getDecorView().setSystemUiVisibility(flags); - WindowCompat.setDecorFitsSystemWindows(window, false); - } + setupStatusBarColors(R.color.keyman_blue, android.R.color.white); Toolbar toolbar = (Toolbar) findViewById(R.id.titlebar); setSupportActionBar(toolbar); diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KMPBrowserActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KMPBrowserActivity.java index 8c02d777c3..e6a2a597cf 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KMPBrowserActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KMPBrowserActivity.java @@ -16,15 +16,12 @@ import android.util.AndroidRuntimeException; import android.util.Log; import android.view.View; import android.view.ViewGroup; -import android.view.Window; import android.webkit.WebChromeClient; import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.Toast; import androidx.appcompat.app.AppCompatActivity; -import androidx.core.content.ContextCompat; -import androidx.core.view.WindowCompat; import com.keyman.engine.BaseActivity; import com.keyman.engine.KMManager; @@ -84,20 +81,7 @@ public class KMPBrowserActivity extends BaseActivity { } setupEdgeToEdge(R.id.kmp_browser_layout); -// setupStatusBarColors(R.color.complementary_5, android.R.color.white); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - Window window = getWindow(); - window.setStatusBarColor(ContextCompat.getColor(this, R.color.keyman_blue)); - boolean isDarkBackground = true; - int flags = window.getDecorView().getSystemUiVisibility(); - if (isDarkBackground) { - flags &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; - } else { - flags |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; - } - window.getDecorView().setSystemUiVisibility(flags); - WindowCompat.setDecorFitsSystemWindows(window, false); - } + setupStatusBarColors(R.color.keyman_blue, android.R.color.white); webView = (WebView) findViewById(R.id.kmpBrowserWebView); webView.getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL); diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeyboardSettingsActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeyboardSettingsActivity.java index 68b0a19fc0..a41690b254 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeyboardSettingsActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeyboardSettingsActivity.java @@ -96,7 +96,6 @@ public final class KeyboardSettingsActivity extends AppCompatActivity { // Display notification to download update if available if (kbd.hasUpdateAvailable()) { hashMap.put(subtitleKey, context.getString(R.string.update_available, kbVersion)); -// icon = String.valueOf(R.drawable.ic_cloud_download); icon = String.valueOf(R.drawable.ic_cloud_download_mode); } hashMap.put(iconKey, icon); @@ -106,7 +105,6 @@ public final class KeyboardSettingsActivity extends AppCompatActivity { hashMap = new HashMap<>(); final String customHelpLink = kbd.getHelpLink(); // Check if app declared FileProvider - //icon = String.valueOf(R.drawable.ic_action_forward); icon = String.valueOf(R.drawable.ic_action_forward_mode); // Don't show help link arrow if File Provider unavailable, or custom help doesn't exist if ( (customHelpLink != null && !FileProviderUtils.exists(context)) || diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MyApp.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanApplication.java similarity index 78% rename from android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MyApp.java rename to android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanApplication.java index ddb494450a..e3a3094eb6 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MyApp.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanApplication.java @@ -1,10 +1,14 @@ +/** + * Copyright (C) SIL International. All rights reserved. + */ package com.tavultesoft.kmapro; import android.app.Application; import android.content.SharedPreferences; + import androidx.appcompat.app.AppCompatDelegate; -public class MyApp extends Application { +public class KeymanApplication extends Application { @Override public void onCreate() { SharedPreferences prefs = getSharedPreferences("settings", MODE_PRIVATE); @@ -12,4 +16,4 @@ public class MyApp extends Application { AppCompatDelegate.setDefaultNightMode(mode); super.onCreate(); } -} \ No newline at end of file +} diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsActivity.java deleted file mode 100644 index fecbc6cddb..0000000000 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsActivity.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.tavultesoft.kmapro; - -import android.os.Bundle; - -import com.keyman.engine.BaseActivity; -import com.keyman.engine.KMManager; - -import androidx.appcompat.widget.Toolbar; - -public class KeymanSettingsActivity extends BaseActivity { - protected static final String installedLanguagesKey = "InstalledLanguages"; - protected static final String installKeyboardOrDictionaryKey = "InstallKeyboardOrDictionary"; - protected static final String sendCrashReport = "SendCrashReport"; - public static final String spacebarTextKey = "SpacebarText"; - public static final String hapticFeedbackKey = "HapticFeedback"; - public static final String oskWithPhysicalKeyboardKey = "ShowOSK"; - - protected KeymanSettingsFragment innerFragment; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setContentView(R.layout.keyman_settings_layout); - - setupEdgeToEdge(R.id.keyman_settings_layout); - setupStatusBarColors(R.color.keyman_blue, android.R.color.white); - - Toolbar toolbar = (Toolbar) findViewById(R.id.keyman_settings_toolbar); - toolbar.setTitleTextColor(getResources().getColor(android.R.color.white)); - toolbar.setTitle(R.string.keyman_settings); - setSupportActionBar(toolbar); - if (getSupportActionBar() != null) { - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - getSupportActionBar().setDisplayShowHomeEnabled(true); - getSupportActionBar().setDisplayShowTitleEnabled(true); - } - - innerFragment = (KeymanSettingsFragment) getSupportFragmentManager().findFragmentById(R.id.keyman_settings_fragment); - - // Scroll to a specific preference if requested (e.g. from sidebar) - String scrollToKey = getIntent().getStringExtra("scrollToPreference"); - if (scrollToKey != null) { - innerFragment.scrollToPreference(scrollToKey); - } - - // For Keyman sites, disable keyboard picker task flag so keyboard picker doesn't dismiss Keyman app - KMManager.dontCloseParentAppOnShowKeyboardPicker(); - } - - @Override - public void onWindowFocusChanged(boolean hasFocus) { - super.onWindowFocusChanged(hasFocus); - - if(hasFocus) { - innerFragment.update(); - } - } -} diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsFragment.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsFragment.java index 3aa149f477..185cde6100 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsFragment.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsFragment.java @@ -44,7 +44,7 @@ public class KeymanSettingsFragment extends PreferenceFragmentCompat { getPreferenceManager().setSharedPreferencesName(getString(R.string.kma_prefs_name)); languagesPreference = new Preference(context); - languagesPreference.setKey(KeymanSettingsActivity.installedLanguagesKey); + languagesPreference.setKey(KeymanSettingsKeys.INSTALLED_LANGUAGES); languagesPreference.setTitle(getInstalledLanguagesText()); languagesPreference.setWidgetLayoutResource(R.layout.preference_icon_layout); Intent languagesIntent = new Intent(context, LanguagesSettingsActivity.class); @@ -52,7 +52,7 @@ public class KeymanSettingsFragment extends PreferenceFragmentCompat { languagesPreference.setIntent(languagesIntent); installKeyboardOrDictionary = new Preference(context); - installKeyboardOrDictionary.setKey(KeymanSettingsActivity.installKeyboardOrDictionaryKey); + installKeyboardOrDictionary.setKey(KeymanSettingsKeys.INSTALL_KEYBOARD_OR_DICTIONARY); installKeyboardOrDictionary.setTitle(getString(R.string.install_keyboard_or_dictionary)); installKeyboardOrDictionary.setWidgetLayoutResource(R.layout.preference_add_icon_layout); Intent installIntent = new Intent(context, KeymanSettingsInstallActivity.class); @@ -120,7 +120,7 @@ public class KeymanSettingsFragment extends PreferenceFragmentCompat { /* Spacebar Caption Preference */ spacebarTextPreference = new ListPreference(context); - spacebarTextPreference.setKey(KeymanSettingsActivity.spacebarTextKey); + spacebarTextPreference.setKey(KeymanSettingsKeys.SPACEBAR_TEXT); spacebarTextPreference.setTitle(getString(R.string.spacebar_caption)); // getString(R.string.change_display_language)); @@ -161,7 +161,7 @@ public class KeymanSettingsFragment extends PreferenceFragmentCompat { }); SwitchPreference oskWithPhysicalKeyboardPreference = new SwitchPreference(context); - oskWithPhysicalKeyboardPreference.setKey(KeymanSettingsActivity.oskWithPhysicalKeyboardKey); + oskWithPhysicalKeyboardPreference.setKey(KeymanSettingsKeys.OSK_WITH_PHYSICAL_KEYBOARD); oskWithPhysicalKeyboardPreference.setTitle(getString(R.string.show_osk)); oskWithPhysicalKeyboardPreference.setSummary(getString(R.string.show_osk_hint)); @@ -169,7 +169,7 @@ public class KeymanSettingsFragment extends PreferenceFragmentCompat { PreferencesManager.kma_prefs_name, Context.MODE_PRIVATE); SharedPreferences.Editor editor = prefs.edit(); boolean showOSK = prefs.getBoolean( - KeymanSettingsActivity.oskWithPhysicalKeyboardKey, false); + KeymanSettingsKeys.OSK_WITH_PHYSICAL_KEYBOARD, false); oskWithPhysicalKeyboardPreference.setDefaultValue(showOSK); oskWithPhysicalKeyboardPreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { @@ -179,14 +179,14 @@ public class KeymanSettingsFragment extends PreferenceFragmentCompat { return false; } - editor.putBoolean(KeymanSettingsActivity.oskWithPhysicalKeyboardKey, (boolean)newValue); + editor.putBoolean(KeymanSettingsKeys.OSK_WITH_PHYSICAL_KEYBOARD, (boolean)newValue); editor.commit(); return true; } }); SwitchPreference hapticFeedbackPreference = new SwitchPreference(context); - hapticFeedbackPreference.setKey(KeymanSettingsActivity.hapticFeedbackKey); + hapticFeedbackPreference.setKey(KeymanSettingsKeys.HAPTIC_FEEDBACK); hapticFeedbackPreference.setTitle(getString(R.string.haptic_feedback)); hapticFeedbackPreference.setDefaultValue(false); hapticFeedbackPreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { @@ -203,7 +203,7 @@ public class KeymanSettingsFragment extends PreferenceFragmentCompat { /* Automatically does the following: SharedPreferences.Editor editor = prefs.edit(); - editor.putBoolean(KeymanSettingsActivity.showBannerKey, isChecked); + editor.putBoolean(GetStartedActivity.showGetStartedKey, isChecked); as part of the default onClick() used by SwitchPreference. */ SwitchPreference getStartedPreference = new SwitchPreference(context); @@ -212,7 +212,7 @@ public class KeymanSettingsFragment extends PreferenceFragmentCompat { getStartedPreference.setDefaultValue(true); SwitchPreference sendCrashReportPreference = new SwitchPreference(context); - sendCrashReportPreference.setKey(KeymanSettingsActivity.sendCrashReport); + sendCrashReportPreference.setKey(KeymanSettingsKeys.SEND_CRASH_REPORT); sendCrashReportPreference.setTitle(getString(R.string.show_send_crash_report)); sendCrashReportPreference.setSummaryOn(getString(R.string.show_send_crash_report_on)); sendCrashReportPreference.setSummaryOff(getString(R.string.show_send_crash_report_off)); @@ -256,11 +256,8 @@ public class KeymanSettingsFragment extends PreferenceFragmentCompat { public void onResume() { super.onResume(); - // The onResume() function isn't called after the default-keyboard selection and cannot fix that - // option, though it is sufficient for handling the system-keyboard enablement option. - // - // As a result, we rely on KeymanSettingsActivity.onWindowFocusChanged to call - // .update() on our behalf. + // The host activity should call update() from onWindowFocusChanged because + // onResume is not sufficient after default-keyboard selection flows. } public void update() { diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsKeys.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsKeys.java new file mode 100644 index 0000000000..c3deb596ce --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsKeys.java @@ -0,0 +1,13 @@ +package com.tavultesoft.kmapro; + +public final class KeymanSettingsKeys { + private KeymanSettingsKeys() { + } + + public static final String INSTALLED_LANGUAGES = "InstalledLanguages"; + public static final String INSTALL_KEYBOARD_OR_DICTIONARY = "InstallKeyboardOrDictionary"; + public static final String SEND_CRASH_REPORT = "SendCrashReport"; + public static final String SPACEBAR_TEXT = "SpacebarText"; + public static final String HAPTIC_FEEDBACK = "HapticFeedback"; + public static final String OSK_WITH_PHYSICAL_KEYBOARD = "ShowOSK"; +} diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/LanguageSettingsActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/LanguageSettingsActivity.java index d9c029488b..887cfb7033 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/LanguageSettingsActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/LanguageSettingsActivity.java @@ -153,7 +153,6 @@ public final class LanguageSettingsActivity extends BaseActivity { updateActiveLexicalModel(); ImageView imageView = (ImageView) layout.findViewById(R.id.image1); - //imageView.setImageResource(R.drawable.ic_action_forward); imageView.setImageResource(R.drawable.ic_action_forward_mode); layout.setEnabled(true); layout.setOnClickListener(new View.OnClickListener() { @@ -334,7 +333,6 @@ public final class LanguageSettingsActivity extends BaseActivity { } holder.text.setText(kbd.getResourceName()); - //holder.img.setImageResource(R.drawable.ic_action_forward); holder.img.setImageResource(R.drawable.ic_action_forward_mode); return convertView; diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/LanguagesSettingsActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/LanguagesSettingsActivity.java index dd2c61e2b2..3e3c6c785f 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/LanguagesSettingsActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/LanguagesSettingsActivity.java @@ -206,7 +206,6 @@ public final class LanguagesSettingsActivity extends BaseActivity { holder.textLang.setText(data.name); holder.img.setImageResource(R.drawable.ic_action_forward_mode); -// holder.img.setImageResource(R.drawable.ic_action_forward); holder.textCount.setText(getContext().getResources().getQuantityString(R.plurals.keyboard_count, data.keyboards.size(), data.keyboards.size())); diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index 5a8e0f0a17..e23b86375e 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -182,8 +182,6 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene @Override protected void onCreate(Bundle savedInstanceState) { - // setTheme(R.style.AppTheme); - SharedPreferences theme_prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE); int savedMode = theme_prefs.getInt(KEY_THEME_MODE, AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM); AppCompatDelegate.setDefaultNightMode(savedMode); @@ -217,7 +215,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene SharedPreferences prefs = getSharedPreferences(getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); KMManager.SpacebarText spacebarText = KMManager.SpacebarText.fromString( - prefs.getString(KeymanSettingsActivity.spacebarTextKey, KMManager.SpacebarText.LANGUAGE_KEYBOARD.toString())); + prefs.getString(KeymanSettingsKeys.SPACEBAR_TEXT, KMManager.SpacebarText.LANGUAGE_KEYBOARD.toString())); KMManager.setSpacebarText(spacebarText); checkHapticFeedback(); @@ -231,29 +229,22 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene navView.requestLayout(); constraintLayout = (ConstraintLayout) findViewById(R.id.constraintLayout); - // setupEdgeToEdge(R.id.constraintLayout); - // setupStatusBarColors(android.R.color.white, R.color.neutral_2); - // START OF FIXED STATUS BAR LOGIC if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { Window window = getWindow(); window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); - // Set the status bar color based on your dynamic color resource window.setStatusBarColor(ContextCompat.getColor(this, R.color.toolbarColor)); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { int flags = window.getDecorView().getSystemUiVisibility(); - // Check if the system is currently in Night Mode boolean isDarkMode = (getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES; if (!isDarkMode) { - // LIGHT MODE: Make icons BLACK so they show up on white flags |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; } else { - // DARK MODE: Make icons WHITE (by removing the light flag) flags &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; } window.getDecorView().setSystemUiVisibility(flags); @@ -267,14 +258,6 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene getSupportActionBar().setDisplayUseLogoEnabled(true); getSupportActionBar().setDisplayShowHomeEnabled(true); getSupportActionBar().setDisplayShowTitleEnabled(true); - // ImageView logo = new ImageView(this); - // logo.setImageResource(R.drawable.keyman_logo); - // Toolbar.LayoutParams params = new Toolbar.LayoutParams( - // Toolbar.LayoutParams.WRAP_CONTENT, - // Toolbar.LayoutParams.WRAP_CONTENT, - // Gravity.CENTER - // ); - // toolbar.addView(logo, params); getSupportActionBar().setLogo(R.drawable.keyman_logo); getSupportActionBar().setDisplayUseLogoEnabled(false); @@ -283,15 +266,12 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene getSupportActionBar().setDisplayUseLogoEnabled(true); getSupportActionBar().setDisplayShowHomeEnabled(true); getSupportActionBar().setLogo(R.drawable.keyman_logo_mode); - // getSupportActionBar().setLogo(R.drawable.keyman_logo); getSupportActionBar().setDisplayShowTitleEnabled(false); getSupportActionBar().setBackgroundDrawable(getActionBarDrawable(this)); drawerLayout = findViewById(R.id.drawer_layout); drawerLayout.setScrimColor(Color.parseColor("#80000000")); - // Initialize drawerToggle WITHOUT toolbar to prevent it from showing up at the - // start drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, R.string.drawer_open, R.string.drawer_close); drawerLayout.addDrawerListener(drawerToggle); drawerToggle.syncState(); @@ -306,96 +286,11 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene if (id == R.id.nav_toggle_show_osk || id == R.id.action_get_started || id == R.id.nav_toggle_send_crash_report) { toggleDrawerSwitch(navigationView, id); - } else if (id == R.id.nav_checkbox_enable_system_keyboard) { - drawerLayout.closeDrawers(); - startActivity(new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS)); - } else if (id == R.id.nav_checkbox_set_default_keyboard) { - drawerLayout.closeDrawers(); - showDefaultKeyboardPickerFromMainPage(); - - } else if (id == R.id.action_update_keyboards) { - drawerLayout.closeDrawers(); - KMManager.getUpdateTool().executeOpenUpdates(); - // Dismiss icon - updateUpdateCountIndicator(0); - final MenuItem _keyboardupdate = navigationView.getMenu().findItem(R.id.action_update_keyboards); - if (_keyboardupdate != null && _keyboardupdate.isVisible()) { - _keyboardupdate.setVisible(false); - } - return true; - } else if (item.getItemId() == R.id.action_settings) { - showSettings(); - return true; - } else if (id == R.id.nav_installed_languages) { - drawerLayout.closeDrawers(); - Intent intent = new Intent(context, LanguagesSettingsActivity.class); - intent.putExtra(KMManager.KMKey_DisplayKeyboardSwitcher, false); - startActivity(intent); - } else if (id == R.id.nav_install_keyboard) { - drawerLayout.closeDrawers(); - startActivity(new Intent(context, KeymanSettingsInstallActivity.class)); - } else if (id == R.id.nav_display_language) { - drawerLayout.closeDrawers(); - startActivity(new Intent(context, KeymanSettingsLocalizeActivity.class)); - } else if (id == R.id.nav_keyboard_height) { - drawerLayout.closeDrawers(); - startActivity(new Intent(context, AdjustKeyboardHeightActivity.class)); - } else if (id == R.id.nav_longpress_delay) { - drawerLayout.closeDrawers(); - startActivity(new Intent(context, AdjustLongpressDelayActivity.class)); - } else if (id == R.id.nav_spacebar_caption) { - drawerLayout.closeDrawers(); - - // Same entries as in KeymanSettingsFragment - CharSequence[] entries = { - getString(R.string.spacebar_caption_language), - getString(R.string.spacebar_caption_keyboard), - getString(R.string.spacebar_caption_language_keyboard), - getString(R.string.spacebar_caption_blank) - }; - - KMManager.SpacebarText[] values = { - KMManager.SpacebarText.LANGUAGE, - KMManager.SpacebarText.KEYBOARD, - KMManager.SpacebarText.LANGUAGE_KEYBOARD, - KMManager.SpacebarText.BLANK - }; - - // Find the currently selected index - KMManager.SpacebarText current = KMManager.getSpacebarText(); - int currentIndex = Arrays.asList(values).indexOf(current); - - new AlertDialog.Builder(context) - .setTitle(getString(R.string.spacebar_caption)) - .setSingleChoiceItems(entries, currentIndex, (dialog, which) -> { - KMManager.setSpacebarText(values[which]); - - // Persist the selection just like KeymanSettingsFragment does - SharedPreferences prefs = getSharedPreferences( - getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); - prefs.edit() - .putString(KeymanSettingsActivity.spacebarTextKey, values[which].toString()) - .apply(); - - dialog.dismiss(); - }) - .setNegativeButton(getString(R.string.label_cancel), null) - .show(); - // } else if (id == R.id.nav_settings) { - // drawerLayout.closeDrawers(); - // startActivity(new Intent(context, KeymanSettingsActivity.class)); - } else if (id == R.id.nav_help) { - drawerLayout.closeDrawers(); - startActivity(new Intent(context, InfoActivity.class)); - } else if (id == R.id.nav_palette) { - drawerLayout.closeDrawers(); - openThemeDialog(); - } else if (id == R.id.nav_about_current_keyboard || id == R.id.nav_help_active_keyboard) { - drawerLayout.closeDrawers(); - showCurrentKeyboardSettings(); } - return true; + + drawerLayout.closeDrawers(); + return handleDrawerNavigationItemSelected(id); } }); updateCurrentKeyboardDrawerItemTitle(navigationView); @@ -678,28 +573,10 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene lastOrientation = newConfig.orientation; } - @SuppressLint("RestrictedApi") - // @Override - // public boolean onPrepareOptionsMenu(final Menu menu) { - // this.menu = menu; - // final MenuItem _overflowMenuItem = menu.findItem(R.id.action_overflow); - // if (_overflowMenuItem != null) { - // MenuItem updateKeyboards = this.menu.findItem(R.id.action_update_keyboards); - // updateUpdateCountIndicator(updateKeyboards, - // KMManager.getUpdateTool().getOpenUpdateCount(), true); - // } - // return super.onPrepareOptionsMenu(menu); - // } - private void updateUpdateCountIndicator(int anUpdateCount) { if (menu == null) { return; } - // final MenuItem _overflowMenuItem = menu.findItem(R.id.action_overflow); - // if (_overflowMenuItem != null) { - // updateUpdateCountIndicator(_overflowMenuItem, anUpdateCount, false); - // } - final MenuItem _keyboardupdate = menu.findItem(R.id.action_update_keyboards); if (_keyboardupdate != null && anUpdateCount > 0) { _keyboardupdate.setVisible(true); @@ -733,7 +610,6 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene @Override public boolean onCreateOptionsMenu(Menu menu) { - // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); this.menu = menu; @@ -752,20 +628,12 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene @Override public boolean onOptionsItemSelected(MenuItem item) { - // Android Gradle 8.0 no longer declares resources final, so can't use switch - // statement here if (item.getItemId() == R.id.action_info) { showInfo(); return true; } else if (item.getItemId() == R.id.action_share) { showShareDialog(); return true; - /* - * Disable Web Browser to investigate Google sign-in - * } else if ((item.getItemId() == R.id.action_web) { - * showWebBrowser(); - * return true; - */ } else if (item.getItemId() == R.id.action_text_size) { showTextSizeDialog(); return true; @@ -780,7 +648,6 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene return true; } else if (item.getItemId() == R.id.action_update_keyboards) { KMManager.getUpdateTool().executeOpenUpdates(); - // Dismiss icon updateUpdateCountIndicator(0); final MenuItem _keyboardupdate = menu.findItem(R.id.action_update_keyboards); if (_keyboardupdate != null && _keyboardupdate.isVisible()) { @@ -789,7 +656,6 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene return true; } else if (item.getItemId() == R.id.action_sidebar) { - // Open the drawer from the end when the hamburger (overflow) icon is clicked if (drawerLayout.isDrawerOpen(GravityCompat.END)) { drawerLayout.closeDrawer(GravityCompat.END); } else { @@ -1210,8 +1076,6 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene * @return String */ private String getTextSizeString() { - // Instead of formatting the number, will truncate formatting and concat the - // actual textSize String label = getString(R.string.text_size).replace("%1$d", ""); return label + KMString.format(" %d", textSize); } @@ -1235,7 +1099,6 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } }); - // dialogBuilder.show(); no dialog final AlertDialog dialog = dialogBuilder.create(); dialog.show(); dialog.getWindow().setBackgroundDrawableResource(R.color.dialogBackground); @@ -1263,13 +1126,13 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene private void checkSendCrashReport() { SharedPreferences prefs = getSharedPreferences(getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); - boolean maySendCrashReport = prefs.getBoolean(KeymanSettingsActivity.sendCrashReport, true); + boolean maySendCrashReport = prefs.getBoolean(KeymanSettingsKeys.SEND_CRASH_REPORT, true); KMManager.setMaySendCrashReport(maySendCrashReport); } private void checkHapticFeedback() { SharedPreferences prefs = getSharedPreferences(getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); - boolean mayHaveHapticFeedback = prefs.getBoolean(KeymanSettingsActivity.hapticFeedbackKey, false); + boolean mayHaveHapticFeedback = prefs.getBoolean(KeymanSettingsKeys.HAPTIC_FEEDBACK, false); KMManager.setHapticFeedback(mayHaveHapticFeedback); } @@ -1451,20 +1314,95 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } private void showSettings() { - Intent settingsIntent = new Intent(this, KeymanSettingsActivity.class); - startActivity(settingsIntent); + if (drawerLayout != null) { + drawerLayout.openDrawer(GravityCompat.END); + } + } + + private boolean handleDrawerNavigationItemSelected(int id) { + if (id == R.id.nav_checkbox_enable_system_keyboard) { + startActivity(new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS)); + } else if (id == R.id.nav_checkbox_set_default_keyboard) { + showDefaultKeyboardPickerFromMainPage(); + } else if (id == R.id.action_update_keyboards) { + KMManager.getUpdateTool().executeOpenUpdates(); + updateUpdateCountIndicator(0); + final MenuItem keyboardUpdate = navigationView.getMenu().findItem(R.id.action_update_keyboards); + if (keyboardUpdate != null && keyboardUpdate.isVisible()) { + keyboardUpdate.setVisible(false); + } + } else if (id == R.id.action_settings) { + showSettings(); + } else if (id == R.id.nav_installed_languages) { + Intent intent = new Intent(context, LanguagesSettingsActivity.class); + intent.putExtra(KMManager.KMKey_DisplayKeyboardSwitcher, false); + startActivity(intent); + } else if (id == R.id.nav_install_keyboard) { + startActivity(new Intent(context, KeymanSettingsInstallActivity.class)); + } else if (id == R.id.nav_display_language) { + startActivity(new Intent(context, KeymanSettingsLocalizeActivity.class)); + } else if (id == R.id.nav_keyboard_height) { + startActivity(new Intent(context, AdjustKeyboardHeightActivity.class)); + } else if (id == R.id.nav_longpress_delay) { + startActivity(new Intent(context, AdjustLongpressDelayActivity.class)); + } else if (id == R.id.nav_spacebar_caption) { + showSpacebarCaptionDialog(); + } else if (id == R.id.nav_help) { + startActivity(new Intent(context, InfoActivity.class)); + } else if (id == R.id.nav_palette) { + openThemeDialog(); + } else if (id == R.id.nav_about_current_keyboard || id == R.id.nav_help_active_keyboard) { + showCurrentKeyboardSettings(); + } + + return true; + } + + private void showSpacebarCaptionDialog() { + CharSequence[] entries = { + getString(R.string.spacebar_caption_language), + getString(R.string.spacebar_caption_keyboard), + getString(R.string.spacebar_caption_language_keyboard), + getString(R.string.spacebar_caption_blank) + }; + + KMManager.SpacebarText[] values = { + KMManager.SpacebarText.LANGUAGE, + KMManager.SpacebarText.KEYBOARD, + KMManager.SpacebarText.LANGUAGE_KEYBOARD, + KMManager.SpacebarText.BLANK + }; + + KMManager.SpacebarText current = KMManager.getSpacebarText(); + int currentIndex = Arrays.asList(values).indexOf(current); + + new AlertDialog.Builder(context) + .setTitle(getString(R.string.spacebar_caption)) + .setSingleChoiceItems(entries, currentIndex, (dialog, which) -> { + KMManager.setSpacebarText(values[which]); + + SharedPreferences prefs = getSharedPreferences( + getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); + prefs.edit() + .putString(KeymanSettingsKeys.SPACEBAR_TEXT, values[which].toString()) + .apply(); + + dialog.dismiss(); + }) + .setNegativeButton(getString(R.string.label_cancel), null) + .show(); } private void initializeDrawerToggleOptions(NavigationView navigationView) { SharedPreferences prefs = getSharedPreferences(getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); bindDrawerSwitch(navigationView, R.id.nav_toggle_show_osk, - prefs.getBoolean(KeymanSettingsActivity.oskWithPhysicalKeyboardKey, false), + prefs.getBoolean(KeymanSettingsKeys.OSK_WITH_PHYSICAL_KEYBOARD, false), new SwitchChangeHandler() { @Override public void onChanged(boolean isChecked) { SharedPreferences.Editor editor = prefs.edit(); - editor.putBoolean(KeymanSettingsActivity.oskWithPhysicalKeyboardKey, isChecked); + editor.putBoolean(KeymanSettingsKeys.OSK_WITH_PHYSICAL_KEYBOARD, isChecked); editor.apply(); } }); @@ -1481,12 +1419,12 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene }); bindDrawerSwitch(navigationView, R.id.nav_toggle_send_crash_report, - prefs.getBoolean(KeymanSettingsActivity.sendCrashReport, true), + prefs.getBoolean(KeymanSettingsKeys.SEND_CRASH_REPORT, true), new SwitchChangeHandler() { @Override public void onChanged(boolean isChecked) { SharedPreferences.Editor editor = prefs.edit(); - editor.putBoolean(KeymanSettingsActivity.sendCrashReport, isChecked); + editor.putBoolean(KeymanSettingsKeys.SEND_CRASH_REPORT, isChecked); editor.apply(); KMManager.setMaySendCrashReport(isChecked); } @@ -1859,7 +1797,6 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene Paint p = new Paint(); p.setStyle(Paint.Style.FILL); - // p.setColor(Color.BLACK); p.setColor(ContextCompat.getColor(context, R.color.toolbarColor)); canvas.drawRect(new Rect(0, 0, width, actionBarHeight), p); diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/SplashScreenActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/SplashScreenActivity.java index fd913ece4e..ce2fc7ed6d 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/SplashScreenActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/SplashScreenActivity.java @@ -3,46 +3,29 @@ package com.tavultesoft.kmapro; import android.content.SharedPreferences; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; -import android.content.res.Configuration; import android.os.Bundle; import android.os.Handler; -import android.app.Activity; import android.content.Intent; import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.app.AppCompatDelegate; - -import com.keyman.engine.util.KMLog; - import java.util.Calendar; public class SplashScreenActivity extends AppCompatActivity { private final static String TAG = "Splash"; // For now, disable this Activity so app is not delayed showing version/copyright text - private static int SPLASH_TIME_OUT = 100; // msec + private static final int SPLASH_TIME_OUT = 0; // msec @Override protected void onCreate(Bundle savedInstanceState) { - SharedPreferences prefs = getSharedPreferences("settings", MODE_PRIVATE); - int savedMode = prefs.getInt("theme_mode", AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM); - - if (savedMode == AppCompatDelegate.MODE_NIGHT_YES) { - setTheme(R.style.AppTheme_BrandedLaunchDark); - } else { - setTheme(R.style.AppTheme_BrandedLaunchLight); - } super.onCreate(savedInstanceState); setContentView(R.layout.activity_splash_screen); TextView version = (TextView)findViewById(R.id.splash_version); - String ver = ""; - PackageInfo pInfo; try { - pInfo = getPackageManager().getPackageInfo(getPackageName(), 0); - ver = String.format("%s: %s", version.getText(), pInfo.versionName); + PackageInfo pInfo = getPackageManager().getPackageInfo(getPackageName(), 0); + String ver = String.format("%s: %s", version.getText(), pInfo.versionName); } catch (PackageManager.NameNotFoundException e) { - KMLog.LogException(TAG, "", e); // Could not get version number } // TODO: Add version and copyright info when Android supports TextViews in the splash screen @@ -53,8 +36,6 @@ public class SplashScreenActivity extends AppCompatActivity { String date = String.format("%s%s", copyright.getText(), year); //copyright.setText(date); - AppCompatDelegate.setDefaultNightMode(savedMode); - new Handler().postDelayed(new Runnable() { @Override public void run() { diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_decrement.xml b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_decrement.xml index 9045283a46..7f67a424a8 100644 --- a/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_decrement.xml +++ b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_decrement.xml @@ -3,4 +3,3 @@ android:src="@drawable/ic_light_dialog_textsize_down" android:tint="@color/iconColor" android:autoMirrored="true" /> - diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_increment.xml b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_increment.xml index a8e10ac44c..04d80ab840 100644 --- a/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_increment.xml +++ b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_increment.xml @@ -3,4 +3,3 @@ android:src="@drawable/ic_light_dialog_textsize_up" android:tint="@color/iconColor" android:autoMirrored="true"/> - diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_checkbox.xml b/android/KMAPro/kMAPro/src/main/res/drawable/ic_checkbox.xml index 1b5e326df4..bd790eec59 100644 --- a/android/KMAPro/kMAPro/src/main/res/drawable/ic_checkbox.xml +++ b/android/KMAPro/kMAPro/src/main/res/drawable/ic_checkbox.xml @@ -8,23 +8,4 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_hamburger.xml b/android/KMAPro/kMAPro/src/main/res/drawable/ic_hamburger.xml deleted file mode 100644 index 73c78665b0..0000000000 --- a/android/KMAPro/kMAPro/src/main/res/drawable/ic_hamburger.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/res/layout/get_started_toolbar.xml b/android/KMAPro/kMAPro/src/main/res/layout/get_started_toolbar.xml index c539581221..10abcc5345 100644 --- a/android/KMAPro/kMAPro/src/main/res/layout/get_started_toolbar.xml +++ b/android/KMAPro/kMAPro/src/main/res/layout/get_started_toolbar.xml @@ -21,7 +21,6 @@ android:layout_marginStart="@dimen/checkbox_margin_left" android:src="@drawable/ic_action_get_started" app:tint="@color/iconColor"/> - - @color/neutral_1 @color/neutral_2 #FFFFFFCF - @color/neutral_1 @color/complementary_1 diff --git a/android/KMAPro/kMAPro/src/main/res/values/dimens.xml b/android/KMAPro/kMAPro/src/main/res/values/dimens.xml index f723410198..d690612371 100644 --- a/android/KMAPro/kMAPro/src/main/res/values/dimens.xml +++ b/android/KMAPro/kMAPro/src/main/res/values/dimens.xml @@ -18,7 +18,6 @@ 350dp 8dp 8dp - 19dp 10dp 32dp diff --git a/android/KMAPro/kMAPro/src/main/res/values/styles.xml b/android/KMAPro/kMAPro/src/main/res/values/styles.xml index 6b1b7fa36e..78a4a47ff9 100644 --- a/android/KMAPro/kMAPro/src/main/res/values/styles.xml +++ b/android/KMAPro/kMAPro/src/main/res/values/styles.xml @@ -38,12 +38,6 @@ --> - - - @@ -74,9 +68,4 @@ 4 end - - - - - - + \ No newline at end of file From a2016ce6d8bef4bbdbc4f82f32a980ce1d344f6a Mon Sep 17 00:00:00 2001 From: Mengchou97 Date: Sat, 6 Jun 2026 23:26:53 +0700 Subject: [PATCH 047/140] following PR suggestion on whitespace changes and revert --- .../com/keyman/android/SystemKeyboard.java | 11 ++- .../com/tavultesoft/kmapro/MainActivity.java | 29 +++----- .../res/layout/fragment_select_language.xml | 19 +++-- .../KMAPro/kMAPro/src/main/res/menu/main.xml | 73 +------------------ .../src/main/res/values-night/styles.xml | 4 - .../kMAPro/src/main/res/values/strings.xml | 3 + 6 files changed, 27 insertions(+), 112 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java index 456b4bea23..5306b0ce32 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java +++ b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java @@ -1,3 +1,7 @@ +/** + * Copyright (C) 2017 SIL International. All rights reserved. + */ + package com.keyman.android; import com.keyman.engine.util.DownloadFileUtils; @@ -36,12 +40,7 @@ import android.content.Intent; import io.sentry.android.core.SentryAndroid; import io.sentry.Sentry; - - /** - * Copyright (C) 2017 SIL International. All rights reserved. - */ - - public class SystemKeyboard extends InputMethodService implements OnKeyboardEventListener { +public class SystemKeyboard extends InputMethodService implements OnKeyboardEventListener { private static View inputView = null; private KMHardwareKeyboardInterpreter interpreter = null; diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index e23b86375e..ba12970725 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -97,8 +97,6 @@ import androidx.core.view.WindowCompat; import androidx.core.view.WindowInsetsControllerCompat; import androidx.drawerlayout.widget.DrawerLayout; -import androidx.drawerlayout.widget.DrawerLayout; - import androidx.core.content.ContextCompat; import android.provider.Settings; @@ -125,15 +123,10 @@ import android.widget.LinearLayout; import android.widget.SeekBar; import android.widget.TextView; import android.widget.Toast; +import androidx.core.view.GravityCompat; import android.view.Gravity; import android.view.inputmethod.InputMethodManager; -import androidx.drawerlayout.widget.DrawerLayout; -import androidx.appcompat.app.ActionBarDrawerToggle; -import com.google.android.material.navigation.NavigationView; -import androidx.core.content.ContextCompat; -import androidx.core.view.GravityCompat; -import android.view.MenuItem; import androidx.appcompat.widget.AppCompatCheckBox; import io.sentry.android.core.SentryAndroid; @@ -187,7 +180,6 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene AppCompatDelegate.setDefaultNightMode(savedMode); getDelegate().applyDayNight(); super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); context = this; checkSendCrashReport(); @@ -255,14 +247,6 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene setSupportActionBar(toolbar); getSupportActionBar().setTitle(null); - getSupportActionBar().setDisplayUseLogoEnabled(true); - getSupportActionBar().setDisplayShowHomeEnabled(true); - getSupportActionBar().setDisplayShowTitleEnabled(true); - getSupportActionBar().setLogo(R.drawable.keyman_logo); - - getSupportActionBar().setDisplayUseLogoEnabled(false); - getSupportActionBar().setDisplayShowHomeEnabled(false); - getSupportActionBar().setDisplayUseLogoEnabled(true); getSupportActionBar().setDisplayShowHomeEnabled(true); getSupportActionBar().setLogo(R.drawable.keyman_logo_mode); @@ -404,6 +388,10 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene return; } else { ClipData userdata = returnIntent.getClipData(); + if (userdata == null) { + checkGetStarted(); + return; + } int len = userdata.getItemCount(); for (int i = 0; i < len; i++) { Uri fileUri = userdata.getItemAt(i).getUri(); @@ -554,7 +542,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene SharedPreferences.Editor editor = prefs.edit(); editor.putString(userTextKey, textView.getText().toString()); editor.putInt(userTextSizeKey, textSize); - editor.commit(); + editor.apply(); } @Override @@ -1353,6 +1341,8 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene openThemeDialog(); } else if (id == R.id.nav_about_current_keyboard || id == R.id.nav_help_active_keyboard) { showCurrentKeyboardSettings(); + } else { + return false; } return true; @@ -1774,7 +1764,6 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene if (progressDialog != null && progressDialog.isShowing()) { progressDialog.dismiss(); } - ; progressDialog = null; } @@ -1931,4 +1920,4 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene outStream.flush(); outStream.close(); } -} +} \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/res/layout/fragment_select_language.xml b/android/KMAPro/kMAPro/src/main/res/layout/fragment_select_language.xml index e06f78593b..dfa5d5f532 100644 --- a/android/KMAPro/kMAPro/src/main/res/layout/fragment_select_language.xml +++ b/android/KMAPro/kMAPro/src/main/res/layout/fragment_select_language.xml @@ -1,15 +1,14 @@ - - + android:layout_height="match_parent" + android:orientation="vertical" + tools:context=".SelectLanguageFragment" > + + diff --git a/android/KMAPro/kMAPro/src/main/res/menu/main.xml b/android/KMAPro/kMAPro/src/main/res/menu/main.xml index 9202c14e1c..6a74a945f5 100644 --- a/android/KMAPro/kMAPro/src/main/res/menu/main.xml +++ b/android/KMAPro/kMAPro/src/main/res/menu/main.xml @@ -4,81 +4,10 @@ xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/res/values-night/styles.xml b/android/KMAPro/kMAPro/src/main/res/values-night/styles.xml index 66b83e54cc..3667b18a9f 100644 --- a/android/KMAPro/kMAPro/src/main/res/values-night/styles.xml +++ b/android/KMAPro/kMAPro/src/main/res/values-night/styles.xml @@ -43,10 +43,6 @@ #424242 - - - - diff --git a/android/KMAPro/kMAPro/src/main/res/values/strings.xml b/android/KMAPro/kMAPro/src/main/res/values/strings.xml index ce8a9cd7e5..066daa5c0a 100644 --- a/android/KMAPro/kMAPro/src/main/res/values/strings.xml +++ b/android/KMAPro/kMAPro/src/main/res/values/strings.xml @@ -309,4 +309,7 @@ Unable to launch web browser + + + Menu From 5fc545ba24e6358e8a4a3a8a0aeafe565368c9c3 Mon Sep 17 00:00:00 2001 From: Mengchou97 Date: Sun, 7 Jun 2026 00:20:15 +0700 Subject: [PATCH 048/140] Add navigation drawer with settings, theme switcher, keyboard toolbar popup, and system keyboard management --- .../kmapro/GetStartedActivity.java | 1 - .../kmapro/KeymanSettingsKeys.java | 3 + .../com/tavultesoft/kmapro/MainActivity.java | 90 +++++++++++-------- 3 files changed, 54 insertions(+), 40 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/GetStartedActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/GetStartedActivity.java index f26c2c7220..ce414fbf26 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/GetStartedActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/GetStartedActivity.java @@ -195,7 +195,6 @@ public class GetStartedActivity extends BaseActivity { String two = String.valueOf(R.drawable.ic_looks_two); String three = String.valueOf(R.drawable.ic_looks_three); String checkbox_on = String.valueOf(R.drawable.ic_checkbox); -// String checkbox_on = String.valueOf(android.R.drawable.checkbox_on_background); String info = String.valueOf(R.drawable.ic_info_outline); List kbList = KMManager.getKeyboardsList(this); diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsKeys.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsKeys.java index c3deb596ce..7045c2d54d 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsKeys.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsKeys.java @@ -1,3 +1,6 @@ +/** + * Copyright (C) 2017 SIL International. All rights reserved. + */ package com.tavultesoft.kmapro; public final class KeymanSettingsKeys { diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index ba12970725..0f33f0db5a 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -97,6 +97,8 @@ import androidx.core.view.WindowCompat; import androidx.core.view.WindowInsetsControllerCompat; import androidx.drawerlayout.widget.DrawerLayout; +import androidx.drawerlayout.widget.DrawerLayout; + import androidx.core.content.ContextCompat; import android.provider.Settings; @@ -123,10 +125,15 @@ import android.widget.LinearLayout; import android.widget.SeekBar; import android.widget.TextView; import android.widget.Toast; -import androidx.core.view.GravityCompat; import android.view.Gravity; import android.view.inputmethod.InputMethodManager; +import androidx.drawerlayout.widget.DrawerLayout; +import androidx.appcompat.app.ActionBarDrawerToggle; +import com.google.android.material.navigation.NavigationView; +import androidx.core.content.ContextCompat; +import androidx.core.view.GravityCompat; +import android.view.MenuItem; import androidx.appcompat.widget.AppCompatCheckBox; import io.sentry.android.core.SentryAndroid; @@ -148,7 +155,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene private ConstraintLayout constraintLayout; private KMTextView textView; - private View keyboardToolbarToggleButton; + private View textPopupToggleButton; private View keyboardToolbarContainer; private boolean isKeyboardToolbarExpanded = false; private boolean suppressOutsideCloseForCurrentTouch = false; @@ -180,6 +187,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene AppCompatDelegate.setDefaultNightMode(savedMode); getDelegate().applyDayNight(); super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); context = this; checkSendCrashReport(); @@ -247,6 +255,14 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene setSupportActionBar(toolbar); getSupportActionBar().setTitle(null); + getSupportActionBar().setDisplayUseLogoEnabled(true); + getSupportActionBar().setDisplayShowHomeEnabled(true); + getSupportActionBar().setDisplayShowTitleEnabled(true); + getSupportActionBar().setLogo(R.drawable.keyman_logo); + + getSupportActionBar().setDisplayUseLogoEnabled(false); + getSupportActionBar().setDisplayShowHomeEnabled(false); + getSupportActionBar().setDisplayUseLogoEnabled(true); getSupportActionBar().setDisplayShowHomeEnabled(true); getSupportActionBar().setLogo(R.drawable.keyman_logo_mode); @@ -388,10 +404,6 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene return; } else { ClipData userdata = returnIntent.getClipData(); - if (userdata == null) { - checkGetStarted(); - return; - } int len = userdata.getItemCount(); for (int i = 0; i < len; i++) { Uri fileUri = userdata.getItemAt(i).getUri(); @@ -406,13 +418,13 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene inputStream.close(); } else if (FileUtils.hasJavaScriptExtension(filename) || FileUtils.hasFontExtension(filename)) { File packagesDir = new File(getDir("data", Context.MODE_PRIVATE) + File.separator + - KMManager.KMDefault_AssetPackages); + KMManager.KMDefault_AssetPackages); if (!packagesDir.exists()) { packagesDir.mkdir(); } File undefinedPackageDir = new File(getDir("data", Context.MODE_PRIVATE) + - File.separator + KMManager.KMDefault_UndefinedPackageID); + File.separator + KMManager.KMDefault_UndefinedPackageID); if (!undefinedPackageDir.exists()) { undefinedPackageDir.mkdir(); } @@ -489,8 +501,8 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene CheckPermissions.requestPermission(this, context); } break; - case "http": - case "https": + case "http" : + case "https" : // Might need to modify link like KMPBrowserActivity if (KMPLink.isKeymanInstallLink(link)) { loadingIntentUri = KMPLink.getKeyboardDownloadLink(link); @@ -499,7 +511,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene KMLog.LogBreadcrumb(TAG, "Installing via HTTPS intent: " + link, true); downloadKMP(loadingIntentUri, KmpInstallMode.Full); break; - case "keyman": + case "keyman" : // TODO: Only accept download links from Keyman browser activities when // universal links work KMLog.LogBreadcrumb(TAG, "Installing via keyman-link intent: " + link, true); @@ -542,7 +554,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene SharedPreferences.Editor editor = prefs.edit(); editor.putString(userTextKey, textView.getText().toString()); editor.putInt(userTextSizeKey, textSize); - editor.apply(); + editor.commit(); } @Override @@ -571,24 +583,25 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } } - private void updateUpdateCountIndicator(MenuItem theItem, int anUpdateCount, boolean aHideMenuitem) { + private void updateUpdateCountIndicator(MenuItem theItem, int anUpdateCount, boolean aHideMenuitem) + { final ViewGroup _rootView = (ViewGroup) theItem.getActionView(); - if (anUpdateCount == 0) { - if (aHideMenuitem) { + if(anUpdateCount==0) { + if(aHideMenuitem) { theItem.setVisible(false); } else if (_rootView != null) { _rootView.findViewById(R.id.update_count_indicator).setVisibility(View.GONE); } } else { - if (aHideMenuitem) { + if(aHideMenuitem) { theItem.setVisible(true); - } else if (_rootView != null) { + } else if(_rootView!=null) { _rootView.findViewById(R.id.update_count_indicator).setVisibility(View.VISIBLE); } } - if (_rootView == null) { + if(_rootView==null) { return; } @@ -673,8 +686,9 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene @Override public boolean dispatchTouchEvent(MotionEvent ev) { int action = ev.getActionMasked(); + // Collapse the keyboard toolbar when the user taps outside of it if (action == MotionEvent.ACTION_DOWN && isKeyboardToolbarExpanded && !suppressOutsideCloseForCurrentTouch) { - boolean touchOnToggle = isTouchInsideView(ev, keyboardToolbarToggleButton); + boolean touchOnToggle = isTouchInsideView(ev, textPopupToggleButton); if (touchOnToggle) { return super.dispatchTouchEvent(ev); } @@ -718,8 +732,8 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene BannerController.setHTMLBanner(this, KeyboardType.KEYBOARD_TYPE_INAPP); resizeTextView(true); updateKeyboardToolbarPosition(); - if (keyboardToolbarToggleButton != null) { - keyboardToolbarToggleButton.setVisibility(View.VISIBLE); + if (textPopupToggleButton != null) { + textPopupToggleButton.setVisibility(View.VISIBLE); } } @@ -727,22 +741,22 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene public void onKeyboardDismissed() { resizeTextView(false); setKeyboardToolbarExpanded(false, false); - if (keyboardToolbarToggleButton != null) { - keyboardToolbarToggleButton.setVisibility(View.GONE); + if (textPopupToggleButton != null) { + textPopupToggleButton.setVisibility(View.GONE); } } private void initializeKeyboardToolbar() { - keyboardToolbarToggleButton = findViewById(R.id.keyboardToolbarToggleButton); + textPopupToggleButton = findViewById(R.id.keyboardToolbarToggleButton); keyboardToolbarContainer = findViewById(R.id.keyboardToolbarContainer); - if (keyboardToolbarToggleButton == null || keyboardToolbarContainer == null) { + if (textPopupToggleButton == null || keyboardToolbarContainer == null) { reportKeyboardToolbarLayoutIssue("Keyboard toolbar layout is missing required views."); return; } setKeyboardToolbarExpanded(false, false); - keyboardToolbarToggleButton.setOnTouchListener((v, event) -> { + textPopupToggleButton.setOnTouchListener((v, event) -> { int action = event.getActionMasked(); if (action == MotionEvent.ACTION_DOWN) { suppressOutsideCloseForCurrentTouch = true; @@ -751,7 +765,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } return false; }); - keyboardToolbarToggleButton.setOnClickListener(v -> setKeyboardToolbarExpanded(!isKeyboardToolbarExpanded, true)); + textPopupToggleButton.setOnClickListener(v -> setKeyboardToolbarExpanded(!isKeyboardToolbarExpanded, true)); View shareButton = findViewById(R.id.keyboardToolbarShareButton); if (shareButton != null) { @@ -792,7 +806,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } private void setKeyboardToolbarExpanded(boolean expanded, boolean animateRotation) { - if (keyboardToolbarContainer == null || keyboardToolbarToggleButton == null) { + if (keyboardToolbarContainer == null || textPopupToggleButton == null) { return; } @@ -801,16 +815,16 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene float targetRotation = expanded ? 0f : 180f; if (animateRotation) { - keyboardToolbarToggleButton.animate().rotation(targetRotation).setDuration(180).start(); + textPopupToggleButton.animate().rotation(targetRotation).setDuration(180).start(); } else { - keyboardToolbarToggleButton.setRotation(targetRotation); + textPopupToggleButton.setRotation(targetRotation); } } private void updateKeyboardToolbarPosition() { int keyboardTopOffset = KMManager.getKeyboardHeight(this) + KMManager.getBannerHeight(this) + getResources().getDimensionPixelSize(R.dimen.keyboard_toolbar_keyboard_gap); - updateBottomMargin(keyboardToolbarToggleButton, keyboardTopOffset); + updateBottomMargin(textPopupToggleButton, keyboardTopOffset); updateBottomMargin(keyboardToolbarContainer, getResources().getDimensionPixelSize(R.dimen.keyboard_toolbar_container_bottom_margin)); } @@ -1341,8 +1355,6 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene openThemeDialog(); } else if (id == R.id.nav_about_current_keyboard || id == R.id.nav_help_active_keyboard) { showCurrentKeyboardSettings(); - } else { - return false; } return true; @@ -1764,6 +1776,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene if (progressDialog != null && progressDialog.isShowing()) { progressDialog.dismiss(); } + progressDialog = null; } @@ -1894,11 +1907,10 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene : KMManager.KMDefault_LanguageID; boolean matchingModel = false; - for (int i = 0; i < lexicalModelsInstalled.size(); i++) { - HashMap lexicalModelInfo = new HashMap<>(lexicalModelsInstalled.get(i)); - if (lexicalModelInfo.get(KMManager.KMKey_LanguageID).equals(langId)) { - matchingModel = true; - } + for(int i=0; ilexicalModelInfo = new HashMap<>(lexicalModelsInstalled.get(i)); + if(lexicalModelInfo.get(KMManager.KMKey_LanguageID).equals(langId)) { + if(matchingModel) { KMManager.addLexicalModel(this, lexicalModelInfo); } @@ -1920,4 +1932,4 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene outStream.flush(); outStream.close(); } -} \ No newline at end of file +} From 2c6f211d4ce5c026a650b635f55853a18c92e172 Mon Sep 17 00:00:00 2001 From: Mengchou97 Date: Sun, 7 Jun 2026 00:23:46 +0700 Subject: [PATCH 049/140] error, so change back --- .../main/java/com/tavultesoft/kmapro/MainActivity.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index 0f33f0db5a..2c85f2d61e 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -1907,10 +1907,11 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene : KMManager.KMDefault_LanguageID; boolean matchingModel = false; - for(int i=0; ilexicalModelInfo = new HashMap<>(lexicalModelsInstalled.get(i)); - if(lexicalModelInfo.get(KMManager.KMKey_LanguageID).equals(langId)) { - if(matchingModel) { + for (int i = 0; i < lexicalModelsInstalled.size(); i++) { + HashMap lexicalModelInfo = new HashMap<>(lexicalModelsInstalled.get(i)); + if (lexicalModelInfo.get(KMManager.KMKey_LanguageID).equals(langId)) { + matchingModel = true; + } KMManager.addLexicalModel(this, lexicalModelInfo); } From 00e0967fa103574cd244ec742a014c9feaa12e77 Mon Sep 17 00:00:00 2001 From: Mengchou97 Date: Wed, 10 Jun 2026 13:49:28 +0700 Subject: [PATCH 050/140] updated Keyman standard header and added comment to the onWindowFocusChanged --- .../com/tavultesoft/kmapro/GetStartedActivity.java | 11 +++++++++++ .../com/tavultesoft/kmapro/KeymanSettingsKeys.java | 8 ++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/GetStartedActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/GetStartedActivity.java index ce414fbf26..7f95881901 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/GetStartedActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/GetStartedActivity.java @@ -177,6 +177,17 @@ public class GetStartedActivity extends BaseActivity { super.onWindowFocusChanged(hasFocus); if (hasFocus) { if (openDefaultKeyboardPickerOnly) { + + // When the user taps 'Set Default Keyboard' from the drawer, + // GetStartedActivity is launched in picker-only mode. But you + // can't open the system keyboard picker directly from a menu + // tap — it requires a window with focus. So we use + // onWindowFocusChanged as the trigger point: first focus opens + // the picker, and when the user dismisses it and focus returns + // to this activity, we call finish() to close it. The activity + // is essentially just a invisible bridge to show the system + // picker. + if (!didAutoOpenDefaultKeyboardPicker) { didAutoOpenDefaultKeyboardPicker = true; InputMethodManager imManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsKeys.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsKeys.java index 7045c2d54d..f2a57798ec 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsKeys.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsKeys.java @@ -1,5 +1,9 @@ -/** - * Copyright (C) 2017 SIL International. All rights reserved. +/* + * Keyman is copyright (C) SIL Global. MIT License. + * + * Created by MengchouChey on 2026-06-10 + * + * (These are the keys used for KeymanSettingsActivity and KeymanSettingsFragment) */ package com.tavultesoft.kmapro; From ecccaa0e336b7d3450db5242c2d24e9b8b814654 Mon Sep 17 00:00:00 2001 From: Mengchou97 Date: Wed, 10 Jun 2026 13:51:42 +0700 Subject: [PATCH 051/140] replace SystemKeyboard.java with the one from master --- .../com/keyman/android/SystemKeyboard.java | 573 +++++++++--------- 1 file changed, 286 insertions(+), 287 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java index 5306b0ce32..97f8ab7d38 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java +++ b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java @@ -7,7 +7,7 @@ package com.keyman.android; import com.keyman.engine.util.DownloadFileUtils; import com.tavultesoft.kmapro.BuildConfig; import com.tavultesoft.kmapro.DefaultLanguageResource; -import com.tavultesoft.kmapro.KeymanSettingsKeys; +import com.tavultesoft.kmapro.KeymanSettingsActivity; import com.tavultesoft.kmapro.PreferencesManager; import com.keyman.engine.KMManager; import com.keyman.engine.KMManager.KeyboardType; @@ -42,317 +42,316 @@ import io.sentry.Sentry; public class SystemKeyboard extends InputMethodService implements OnKeyboardEventListener { - private static View inputView = null; - private KMHardwareKeyboardInterpreter interpreter = null; - private int inputType = InputType.TYPE_NULL; - private int lastOrientation = Configuration.ORIENTATION_UNDEFINED; + private static View inputView = null; + private static ExtractedText exText = null; + private KMHardwareKeyboardInterpreter interpreter = null; + private int inputType = InputType.TYPE_NULL; + private int lastOrientation = Configuration.ORIENTATION_UNDEFINED; - private static final String TAG = "SystemKeyboard"; + private static final String TAG = "SystemKeyboard"; - /** - * Main initialization of the input method component. Be sure to call - * to super class. - */ - @Override - public void onCreate() { - super.onCreate(); + /** + * Main initialization of the input method component. Be sure to call + * to super class. + */ + @Override + public void onCreate() { + super.onCreate(); - if (DependencyUtil.libraryExists(LibraryType.SENTRY) && !Sentry.isEnabled()) { - Log.d(TAG, "Initializing Sentry"); - SentryAndroid.init(getApplicationContext(), options -> { - options.setEnableAutoSessionTracking(false); - options.setRelease(com.tavultesoft.kmapro.BuildConfig.KEYMAN_VERSION_GIT_TAG); - options.setEnvironment(com.tavultesoft.kmapro.BuildConfig.KEYMAN_VERSION_ENVIRONMENT); - }); + if (DependencyUtil.libraryExists(LibraryType.SENTRY) && !Sentry.isEnabled()) { + Log.d(TAG, "Initializing Sentry"); + SentryAndroid.init(getApplicationContext(), options -> { + options.setEnableAutoSessionTracking(false); + options.setRelease(com.tavultesoft.kmapro.BuildConfig.KEYMAN_VERSION_GIT_TAG); + options.setEnvironment(com.tavultesoft.kmapro.BuildConfig.KEYMAN_VERSION_ENVIRONMENT); + }); + } + if (BuildConfig.DEBUG) { + KMManager.setDebugMode(true); + } + KMManager.addKeyboardEventListener(this); + Context context = getApplicationContext(); + KMManager.initialize(context, KeyboardType.KEYBOARD_TYPE_SYSTEM); + DefaultLanguageResource.install(context); + interpreter = new KMHardwareKeyboardInterpreter(context, KeyboardType.KEYBOARD_TYPE_SYSTEM); + KMManager.setInputMethodService(this); // for HW interface + + SharedPreferences prefs = context.getSharedPreferences(context.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); + KMManager.SpacebarText spacebarText = KMManager.SpacebarText.fromString(prefs.getString(KeymanSettingsActivity.spacebarTextKey, KMManager.SpacebarText.LANGUAGE_KEYBOARD.toString())); + KMManager.setSpacebarText(spacebarText); + + // Set the system keyboard HTML banner + BannerController.setHTMLBanner(this, KeyboardType.KEYBOARD_TYPE_SYSTEM); + + boolean mayHaveHapticFeedback = prefs.getBoolean(KeymanSettingsActivity.hapticFeedbackKey, false); + KMManager.setHapticFeedback(mayHaveHapticFeedback); + + // Checking for updates should never be allowed to crash the keyboard. + // Just silently fail if this occurs. + if(DownloadFileUtils.getDownloadManager(this) != null) { + // Will try to emit a toast if it fails - i.e., is not silent. + KMManager.executeResourceUpdate(this); + } + } + + @Override + public void onDestroy() { + inputView = null; + KMManager.removeKeyboardEventListener(this); + interpreter = null; // Throw it away, since we're losing our application's context. + KMManager.onDestroy(); + super.onDestroy(); + } + + /** + * This is the point where you can do all of your UI initialization. It + * is called after creation and any configuration change. + */ + @Override + public void onInitializeInterface() { + super.onInitializeInterface(); + } + + /** + * Called by the framework when your view for creating input needs to + * be generated. This will be called the first time your input method + * is displayed, and every time it needs to be re-created such as due to + * a configuration change. + */ + @Override + public View onCreateInputView() { + if (inputView == null) { + inputView = KMManager.createInputView(this); + } + + ViewGroup parent = (ViewGroup) inputView.getParent(); + if (parent != null) { + parent.removeView(inputView); + } + + return inputView; + } + + /** + * Deal with the editor reporting movement of its cursor. + */ + @Override + public void onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd) { + super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, candidatesStart, candidatesEnd); + KMManager.updateSelectionRange(KMManager.KeyboardType.KEYBOARD_TYPE_SYSTEM); + } + + private void sendCurrentFontName() { + Keyboard keyboardInfo = KMManager.getCurrentKeyboardInfo(this); + if (keyboardInfo != null) { + String fontName = keyboardInfo.getFont(); + + Intent intent; + if (BuildConfig.DEBUG) { + intent = new Intent("com.tavultesoft.kmapro.debug.keyboard_changed"); + } else { + intent = new Intent("com.tavultesoft.kmapro.keyboard_changed"); } - if (BuildConfig.DEBUG) { - KMManager.setDebugMode(true); - } - KMManager.addKeyboardEventListener(this); - Context context = getApplicationContext(); - KMManager.initialize(context, KeyboardType.KEYBOARD_TYPE_SYSTEM); - DefaultLanguageResource.install(context); - interpreter = new KMHardwareKeyboardInterpreter(context, KeyboardType.KEYBOARD_TYPE_SYSTEM); - KMManager.setInputMethodService(this); // for HW interface + intent.putExtra("fontName", fontName); + sendBroadcast(intent); + } + } - SharedPreferences prefs = context.getSharedPreferences(context.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); - KMManager.SpacebarText spacebarText = KMManager.SpacebarText.fromString(prefs.getString(KeymanSettingsKeys.SPACEBAR_TEXT, KMManager.SpacebarText.LANGUAGE_KEYBOARD.toString())); - KMManager.setSpacebarText(spacebarText); + /** + * This is the main point where we do our initialization of the input method + * to begin operating on an application. At this point we have been + * bound to the client, and are now receiving all of the detailed information + * about the target of our edits. + */ + @Override + public void onStartInput(EditorInfo attribute, boolean restarting) { + attribute.imeOptions |= EditorInfo.IME_FLAG_NO_EXTRACT_UI | EditorInfo.IME_FLAG_NO_FULLSCREEN; + super.onStartInput(attribute, restarting); + KMManager.onStartInput(attribute, restarting); + KMManager.resetContext(KeyboardType.KEYBOARD_TYPE_SYSTEM); + Context appContext = getApplicationContext(); + // Temporarily disable predictions on certain fields (e.g. hidden password field or numeric) + inputType = attribute.inputType; + KMManager.setPredictionsSuspended(inputType, KeyboardType.KEYBOARD_TYPE_SYSTEM); + if (KMManager.getPredictionsSuspended(KeyboardType.KEYBOARD_TYPE_SYSTEM)) { + KMManager.setBannerOptions(false); // Set the system keyboard HTML banner BannerController.setHTMLBanner(this, KeyboardType.KEYBOARD_TYPE_SYSTEM); - - boolean mayHaveHapticFeedback = prefs.getBoolean(KeymanSettingsKeys.HAPTIC_FEEDBACK, false); - KMManager.setHapticFeedback(mayHaveHapticFeedback); - - // Checking for updates should never be allowed to crash the keyboard. - // Just silently fail if this occurs. - if(DownloadFileUtils.getDownloadManager(this) != null) { - // Will try to emit a toast if it fails - i.e., is not silent. - KMManager.executeResourceUpdate(this); - } - } - - - @Override - public void onDestroy() { - inputView = null; - KMManager.removeKeyboardEventListener(this); - interpreter = null; // Throw it away, since we're losing our application's context. - KMManager.onDestroy(); - super.onDestroy(); - } - - /** - * This is the point where you can do all of your UI initialization. It - * is called after creation and any configuration change. - */ - @Override - public void onInitializeInterface() { - super.onInitializeInterface(); - } - - /** - * Called by the framework when your view for creating input needs to - * be generated. This will be called the first time your input method - * is displayed, and every time it needs to be re-created such as due to - * a configuration change. - */ - @Override - public View onCreateInputView() { - if (inputView == null) { - inputView = KMManager.createInputView(this); - } - - ViewGroup parent = (ViewGroup) inputView.getParent(); - if (parent != null) { - parent.removeView(inputView); - } - - return inputView; - } - - /** - * Deal with the editor reporting movement of its cursor. - */ - @Override - public void onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd) { - super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, candidatesStart, candidatesEnd); - KMManager.updateSelectionRange(KMManager.KeyboardType.KEYBOARD_TYPE_SYSTEM); - } - - private void sendCurrentFontName() { - Keyboard keyboardInfo = KMManager.getCurrentKeyboardInfo(this); - if (keyboardInfo != null) { - String fontName = keyboardInfo.getFont(); - - Intent intent; - if (BuildConfig.DEBUG) { - intent = new Intent("com.tavultesoft.kmapro.debug.keyboard_changed"); - } else { - intent = new Intent("com.tavultesoft.kmapro.keyboard_changed"); - } - intent.putExtra("fontName", fontName); - sendBroadcast(intent); - } - } - - /** - * This is the main point where we do our initialization of the input method - * to begin operating on an application. At this point we have been - * bound to the client, and are now receiving all of the detailed information - * about the target of our edits. - */ - @Override - public void onStartInput(EditorInfo attribute, boolean restarting) { - attribute.imeOptions |= EditorInfo.IME_FLAG_NO_EXTRACT_UI | EditorInfo.IME_FLAG_NO_FULLSCREEN; - super.onStartInput(attribute, restarting); - KMManager.onStartInput(attribute, restarting); - KMManager.resetContext(KeyboardType.KEYBOARD_TYPE_SYSTEM); - - Context appContext = getApplicationContext(); - // Temporarily disable predictions on certain fields (e.g. hidden password field or numeric) - inputType = attribute.inputType; - KMManager.setPredictionsSuspended(inputType, KeyboardType.KEYBOARD_TYPE_SYSTEM); - if (KMManager.getPredictionsSuspended(KeyboardType.KEYBOARD_TYPE_SYSTEM)) { + } else if (KMManager.isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_SYSTEM)){ + // Check if predictions needs to be re-enabled per Settings preference + Keyboard kbInfo = KMManager.getCurrentKeyboardInfo(appContext); + if (kbInfo != null) { + String langId = kbInfo.getLanguageID(); + SharedPreferences prefs = appContext.getSharedPreferences(appContext.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); + int maySuggest = prefs.getInt(KMManager.getLanguageAutoCorrectionPreferenceKey(langId), KMManager.KMDefault_Suggestion); + // Enable banner if maySuggest is not SuggestionType.SUGGESTIONS_DISABLED (0) + KMManager.setBannerOptions(maySuggest != SuggestionType.SUGGESTIONS_DISABLED.toInt()); + } else { KMManager.setBannerOptions(false); - // Set the system keyboard HTML banner - BannerController.setHTMLBanner(this, KeyboardType.KEYBOARD_TYPE_SYSTEM); - } else if (KMManager.isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_SYSTEM)){ - // Check if predictions needs to be re-enabled per Settings preference - Keyboard kbInfo = KMManager.getCurrentKeyboardInfo(appContext); - if (kbInfo != null) { - String langId = kbInfo.getLanguageID(); - SharedPreferences prefs = appContext.getSharedPreferences(appContext.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); - int maySuggest = prefs.getInt(KMManager.getLanguageAutoCorrectionPreferenceKey(langId), KMManager.KMDefault_Suggestion); - // Enable banner if maySuggest is not SuggestionType.SUGGESTIONS_DISABLED (0) - KMManager.setBannerOptions(maySuggest != SuggestionType.SUGGESTIONS_DISABLED.toInt()); - } else { - KMManager.setBannerOptions(false); - } } + } - // Determine special handling for ENTER key - KMManager.setEnterMode(attribute.imeOptions, inputType); + // Determine special handling for ENTER key + KMManager.setEnterMode(attribute.imeOptions, inputType); - - InputConnection ic = getCurrentInputConnection(); - if (ic != null) { - ExtractedText icText = ic.getExtractedText(new ExtractedTextRequest(), 0); + InputConnection ic = getCurrentInputConnection(); + if (ic != null) { + ExtractedText icText = ic.getExtractedText(new ExtractedTextRequest(), 0); /* We do sometimes receive null `icText.text`, even though getExtractedText() docs does not list this as a possible return value, so we test for that as well (#11479) */ - if (icText != null && icText.text != null) { - // Update the text selection but ignore the returned statuses - KMManager.updateText(KeyboardType.KEYBOARD_TYPE_SYSTEM, icText.text.toString()); - KMManager.updateSelectionRange(KeyboardType.KEYBOARD_TYPE_SYSTEM); - } - } - - // Select numeric layer if applicable - if (KMManager.isNumericField(inputType)) { - KMManager.setNumericLayer(KeyboardType.KEYBOARD_TYPE_SYSTEM); - } - - if (KMManager.isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_SYSTEM)) { - sendCurrentFontName(); + if (icText != null && icText.text != null) { + boolean didUpdateText = KMManager.updateText(KeyboardType.KEYBOARD_TYPE_SYSTEM, icText.text.toString()); + boolean didUpdateSelection = KMManager.updateSelectionRange(KeyboardType.KEYBOARD_TYPE_SYSTEM); + if (!didUpdateText || !didUpdateSelection) + exText = icText; } } - @Override - public void onStartInputView(EditorInfo attribute, boolean restarting) { - super.onStartInputView(attribute, restarting); - setInputView(onCreateInputView()); - - // Update the input type - inputType = attribute.inputType; + // Select numeric layer if applicable + if (KMManager.isNumericField(inputType)) { + KMManager.setNumericLayer(KeyboardType.KEYBOARD_TYPE_SYSTEM); } - @Override - public void onUpdateExtractingVisibility(EditorInfo ei) { - super.onUpdateExtractingVisibility(ei); - } - - @Override - public void onConfigureWindow(Window win, boolean isFullscreen, boolean isCandidatesOnly) { - super.onConfigureWindow(win, isFullscreen, isCandidatesOnly); - - // We don't currently use isFullscreen or isCandidatesOnly; we always want to MATCH_PARENT, - // unlike the default for height which is WRAP_CONTENT. We then adjust the touchable area - // in `onCalculateInsets` - win.setLayout(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT); - } - - @Override - public void onComputeInsets(InputMethodService.Insets outInsets) { - super.onComputeInsets(outInsets); - - // This method (likely) includes the IME equivalent to `onResume` for `Activity`-based classes, - // making it an important time to detect orientation changes. - Context appContext = getApplicationContext(); - int newOrientation = KMManager.getOrientation(appContext); - if(newOrientation != lastOrientation) { - lastOrientation = newOrientation; - Configuration newConfig = this.getResources().getConfiguration(); - KMManager.onConfigurationChanged(newConfig); - } - - // Update the touchable region of the Keyman keyboard - Point size = KMManager.getWindowSize(getApplicationContext()); - - int inputViewHeight = 0; - if (inputView != null) { - inputViewHeight = inputView.getHeight(); - } - - int navigationHeight = KMManager.getNavigationBarHeight(this, KeyboardType.KEYBOARD_TYPE_SYSTEM); - int bannerHeight = KMManager.getBannerHeight(this); - int kbHeight = KMManager.getKeyboardHeight(this); - outInsets.contentTopInsets = inputViewHeight - bannerHeight - kbHeight - navigationHeight; - outInsets.visibleTopInsets = outInsets.contentTopInsets; - outInsets.touchableInsets = InputMethodService.Insets.TOUCHABLE_INSETS_REGION; - outInsets.touchableRegion.set(0, outInsets.contentTopInsets, size.x, size.y); - } - - @Override - public void onKeyboardLoaded(KeyboardType keyboardType) { - // Do nothing - } - - @Override - public void onKeyboardChanged(String newKeyboard) { - // Refresh banner theme - BannerController.setHTMLBanner(this, KeyboardType.KEYBOARD_TYPE_SYSTEM); - KMManager.showSystemKeyboard(); + if (KMManager.isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_SYSTEM)) { sendCurrentFontName(); } - - @Override - public void onKeyboardShown() { - // Refresh banner theme - BannerController.setHTMLBanner(this, KeyboardType.KEYBOARD_TYPE_SYSTEM); - } - - @Override - public void onKeyboardDismissed() { - // Do nothing - } - - - @Override - public boolean onEvaluateInputViewShown() { - // On Android API 36+, the OSK defaults to not appearing when a physical keyboard is connected. - // If the default implementation returns true, recommend honoring it - // Reference: https://android.googlesource.com/platform/frameworks/base/+/7b739a8%5E%21/ - if (super.onEvaluateInputViewShown()) { - return true; - }; - - Context context = getApplicationContext(); - SharedPreferences prefs = context.getSharedPreferences(PreferencesManager.kma_prefs_name, Context.MODE_PRIVATE); - boolean showOSK = prefs.getBoolean(KeymanSettingsKeys.OSK_WITH_PHYSICAL_KEYBOARD, false); - return showOSK; - } - - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - // Determine if Physical keystroke should be passed off - if (inputType == InputType.TYPE_NULL) { - return false; // Revert to default handling - } - - if (event.getAction() == KeyEvent.ACTION_DOWN) { - switch (keyCode) { - case KeyEvent.KEYCODE_BACK: - // Dismiss the keyboard if currently shown - if (isInputViewShown()) { - KMManager.hideSystemKeyboard(); - return true; - } - break; - } - } - - return interpreter.onKeyDown(keyCode, event); // if false, will revert to default handling. - } - - @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { - return interpreter.onKeyUp(keyCode, event); - } - - @Override - public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) { - return interpreter.onKeyMultiple(keyCode, count, event); - } - - @Override - public boolean onKeyLongPress(int keyCode, KeyEvent event) { - return interpreter.onKeyLongPress(keyCode, event); - } } + @Override + public void onStartInputView(EditorInfo attribute, boolean restarting) { + super.onStartInputView(attribute, restarting); + setInputView(onCreateInputView()); + // Update the input type + inputType = attribute.inputType; + } + @Override + public void onUpdateExtractingVisibility(EditorInfo ei) { + super.onUpdateExtractingVisibility(ei); + } + + @Override + public void onConfigureWindow(Window win, boolean isFullscreen, boolean isCandidatesOnly) { + super.onConfigureWindow(win, isFullscreen, isCandidatesOnly); + + // We don't currently use isFullscreen or isCandidatesOnly; we always want to MATCH_PARENT, + // unlike the default for height which is WRAP_CONTENT. We then adjust the touchable area + // in `onCalculateInsets` + win.setLayout(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT); + } + + @Override + public void onComputeInsets(InputMethodService.Insets outInsets) { + super.onComputeInsets(outInsets); + + // This method (likely) includes the IME equivalent to `onResume` for `Activity`-based classes, + // making it an important time to detect orientation changes. + Context appContext = getApplicationContext(); + int newOrientation = KMManager.getOrientation(appContext); + if(newOrientation != lastOrientation) { + lastOrientation = newOrientation; + Configuration newConfig = this.getResources().getConfiguration(); + KMManager.onConfigurationChanged(newConfig); + } + + // Update the touchable region of the Keyman keyboard + Point size = KMManager.getWindowSize(getApplicationContext()); + + int inputViewHeight = 0; + if (inputView != null) { + inputViewHeight = inputView.getHeight(); + } + + int navigationHeight = KMManager.getNavigationBarHeight(this, KeyboardType.KEYBOARD_TYPE_SYSTEM); + int bannerHeight = KMManager.getBannerHeight(this); + int kbHeight = KMManager.getKeyboardHeight(this); + outInsets.contentTopInsets = inputViewHeight - bannerHeight - kbHeight - navigationHeight; + outInsets.visibleTopInsets = outInsets.contentTopInsets; + outInsets.touchableInsets = InputMethodService.Insets.TOUCHABLE_INSETS_REGION; + outInsets.touchableRegion.set(0, outInsets.contentTopInsets, size.x, size.y); + } + + @Override + public void onKeyboardLoaded(KeyboardType keyboardType) { + if (keyboardType == KeyboardType.KEYBOARD_TYPE_SYSTEM) { + if (exText != null) + exText = null; + } + } + + @Override + public void onKeyboardChanged(String newKeyboard) { + // Refresh banner theme + BannerController.setHTMLBanner(this, KeyboardType.KEYBOARD_TYPE_SYSTEM); + KMManager.showSystemKeyboard(); + sendCurrentFontName(); + } + + @Override + public void onKeyboardShown() { + // Refresh banner theme + BannerController.setHTMLBanner(this, KeyboardType.KEYBOARD_TYPE_SYSTEM); + } + + @Override + public void onKeyboardDismissed() { + // Do nothing + } + + @Override + public boolean onEvaluateInputViewShown() { + // On Android API 36+, the OSK defaults to not appearing when a physical keyboard is connected. + // If the default implementation returns true, recommend honoring it + // Reference: https://android.googlesource.com/platform/frameworks/base/+/7b739a8%5E%21/ + if (super.onEvaluateInputViewShown()) { + return true; + }; + + Context context = getApplicationContext(); + SharedPreferences prefs = context.getSharedPreferences(PreferencesManager.kma_prefs_name, Context.MODE_PRIVATE); + boolean showOSK = prefs.getBoolean(KeymanSettingsActivity.oskWithPhysicalKeyboardKey, false); + return showOSK; + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + // Determine if Physical keystroke should be passed off + if (inputType == InputType.TYPE_NULL) { + return false; // Revert to default handling + } + + if (event.getAction() == KeyEvent.ACTION_DOWN) { + switch (keyCode) { + case KeyEvent.KEYCODE_BACK: + // Dismiss the keyboard if currently shown + if (isInputViewShown()) { + KMManager.hideSystemKeyboard(); + return true; + } + break; + } + } + + return interpreter.onKeyDown(keyCode, event); // if false, will revert to default handling. + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + return interpreter.onKeyUp(keyCode, event); + } + + @Override + public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) { + return interpreter.onKeyMultiple(keyCode, count, event); + } + + @Override + public boolean onKeyLongPress(int keyCode, KeyEvent event) { + return interpreter.onKeyLongPress(keyCode, event); + } +} \ No newline at end of file From 81427760127418b28ccf7366c848137f6b4c9ff9 Mon Sep 17 00:00:00 2001 From: Mengchou97 Date: Wed, 10 Jun 2026 14:09:08 +0700 Subject: [PATCH 052/140] replace SystemKeyboard.java with the one from master but updated the keymansettingactivity to keymansettingkeys --- .../com/keyman/android/SystemKeyboard.java | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java index 97f8ab7d38..8b7d374d9a 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java +++ b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java @@ -7,7 +7,7 @@ package com.keyman.android; import com.keyman.engine.util.DownloadFileUtils; import com.tavultesoft.kmapro.BuildConfig; import com.tavultesoft.kmapro.DefaultLanguageResource; -import com.tavultesoft.kmapro.KeymanSettingsActivity; +import com.tavultesoft.kmapro.KeymanSettingsKeys; import com.tavultesoft.kmapro.PreferencesManager; import com.keyman.engine.KMManager; import com.keyman.engine.KMManager.KeyboardType; @@ -43,7 +43,6 @@ import io.sentry.Sentry; public class SystemKeyboard extends InputMethodService implements OnKeyboardEventListener { private static View inputView = null; - private static ExtractedText exText = null; private KMHardwareKeyboardInterpreter interpreter = null; private int inputType = InputType.TYPE_NULL; private int lastOrientation = Configuration.ORIENTATION_UNDEFINED; @@ -77,13 +76,13 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven KMManager.setInputMethodService(this); // for HW interface SharedPreferences prefs = context.getSharedPreferences(context.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); - KMManager.SpacebarText spacebarText = KMManager.SpacebarText.fromString(prefs.getString(KeymanSettingsActivity.spacebarTextKey, KMManager.SpacebarText.LANGUAGE_KEYBOARD.toString())); + KMManager.SpacebarText spacebarText = KMManager.SpacebarText.fromString(prefs.getString(KeymanSettingsKeys.SPACEBAR_TEXT, KMManager.SpacebarText.LANGUAGE_KEYBOARD.toString())); KMManager.setSpacebarText(spacebarText); // Set the system keyboard HTML banner BannerController.setHTMLBanner(this, KeyboardType.KEYBOARD_TYPE_SYSTEM); - boolean mayHaveHapticFeedback = prefs.getBoolean(KeymanSettingsActivity.hapticFeedbackKey, false); + boolean mayHaveHapticFeedback = prefs.getBoolean(KeymanSettingsKeys.HAPTIC_FEEDBACK, false); KMManager.setHapticFeedback(mayHaveHapticFeedback); // Checking for updates should never be allowed to crash the keyboard. @@ -204,10 +203,9 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven return value, so we test for that as well (#11479) */ if (icText != null && icText.text != null) { - boolean didUpdateText = KMManager.updateText(KeyboardType.KEYBOARD_TYPE_SYSTEM, icText.text.toString()); - boolean didUpdateSelection = KMManager.updateSelectionRange(KeyboardType.KEYBOARD_TYPE_SYSTEM); - if (!didUpdateText || !didUpdateSelection) - exText = icText; + // Update the text selection but ignore the returned statuses + KMManager.updateText(KeyboardType.KEYBOARD_TYPE_SYSTEM, icText.text.toString()); + KMManager.updateSelectionRange(KeyboardType.KEYBOARD_TYPE_SYSTEM); } } @@ -278,10 +276,7 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven @Override public void onKeyboardLoaded(KeyboardType keyboardType) { - if (keyboardType == KeyboardType.KEYBOARD_TYPE_SYSTEM) { - if (exText != null) - exText = null; - } + // Do nothing } @Override @@ -314,7 +309,7 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven Context context = getApplicationContext(); SharedPreferences prefs = context.getSharedPreferences(PreferencesManager.kma_prefs_name, Context.MODE_PRIVATE); - boolean showOSK = prefs.getBoolean(KeymanSettingsActivity.oskWithPhysicalKeyboardKey, false); + boolean showOSK = prefs.getBoolean(KeymanSettingsKeys.OSK_WITH_PHYSICAL_KEYBOARD, false); return showOSK; } From 2c7a61d35cc61a50e439a1089374cf005b15dc83 Mon Sep 17 00:00:00 2001 From: Mengchou97 Date: Wed, 10 Jun 2026 14:29:03 +0700 Subject: [PATCH 053/140] addressing the issues with keymansettingfragments --- .../tavultesoft/kmapro/KeymanSettingsFragment.java | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsFragment.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsFragment.java index 185cde6100..ef5a27211f 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsFragment.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsFragment.java @@ -200,12 +200,6 @@ public class KeymanSettingsFragment extends PreferenceFragmentCompat { } }); - /* - Automatically does the following: - SharedPreferences.Editor editor = prefs.edit(); - editor.putBoolean(GetStartedActivity.showGetStartedKey, isChecked); - as part of the default onClick() used by SwitchPreference. - */ SwitchPreference getStartedPreference = new SwitchPreference(context); getStartedPreference.setKey(GetStartedActivity.showGetStartedKey); getStartedPreference.setTitle(String.format(getString(R.string.show_get_started), getString(R.string.get_started))); @@ -256,8 +250,11 @@ public class KeymanSettingsFragment extends PreferenceFragmentCompat { public void onResume() { super.onResume(); - // The host activity should call update() from onWindowFocusChanged because - // onResume is not sufficient after default-keyboard selection flows. + // The onResume() function isn't called after the default-keyboard selection and cannot fix that + // option, though it is sufficient for handling the system-keyboard enablement option. + // + // As a result, we rely on KeymanSettingsActivity.onWindowFocusChanged to call + // .update() on our behalf. } public void update() { From ecd3f65372909a69db157880bf011cc673372e72 Mon Sep 17 00:00:00 2001 From: Mengchou97 Date: Wed, 10 Jun 2026 14:31:19 +0700 Subject: [PATCH 054/140] added the cmt back --- .../java/com/tavultesoft/kmapro/KeymanSettingsFragment.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsFragment.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsFragment.java index ef5a27211f..cc31c8ab26 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsFragment.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsFragment.java @@ -200,6 +200,12 @@ public class KeymanSettingsFragment extends PreferenceFragmentCompat { } }); + /* + Automatically does the following: + SharedPreferences.Editor editor = prefs.edit(); + editor.putBoolean(KeymanSettingsActivity.showBannerKey, isChecked); + as part of the default onClick() used by SwitchPreference. + */ SwitchPreference getStartedPreference = new SwitchPreference(context); getStartedPreference.setKey(GetStartedActivity.showGetStartedKey); getStartedPreference.setTitle(String.format(getString(R.string.show_get_started), getString(R.string.get_started))); From 9b930ea868440960ecd2b9dff204de99fa9deea1 Mon Sep 17 00:00:00 2001 From: Mengchou97 Date: Wed, 10 Jun 2026 15:16:57 +0700 Subject: [PATCH 055/140] removed unused codes --- .../kmapro/KeymanSettingsKeys.java | 4 +++- .../com/tavultesoft/kmapro/MainActivity.java | 22 ++++--------------- 2 files changed, 7 insertions(+), 19 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsKeys.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsKeys.java index f2a57798ec..9055307b6d 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsKeys.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsKeys.java @@ -2,8 +2,10 @@ * Keyman is copyright (C) SIL Global. MIT License. * * Created by MengchouChey on 2026-06-10 + * Internship Program, 9th Feb - 27th Apr 2026 + * + * These are the keys used to replace KeymanSettingsActivity and KeymanSettingsFragment * - * (These are the keys used for KeymanSettingsActivity and KeymanSettingsFragment) */ package com.tavultesoft.kmapro; diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index 2c85f2d61e..0e64f5f285 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -51,16 +51,13 @@ import com.keyman.engine.util.WebViewUtils.SystemWebViewStatus; import android.app.ProgressDialog; import android.content.pm.PackageManager; - import android.graphics.Color; import android.net.Uri; import android.net.Uri.Builder; import android.os.Build; import android.os.Bundle; - import android.os.Handler; import android.os.ParcelFileDescriptor; - import android.annotation.SuppressLint; import android.annotation.TargetApi; @@ -83,7 +80,6 @@ import android.content.SharedPreferences; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.Canvas; - import android.graphics.Paint; import android.graphics.Point; import android.graphics.Rect; @@ -205,8 +201,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene KMManager.setDebugMode(true); } - // Verify WebView installed and enabled before attempting to initialize - // KMManager + // Verify WebView installed and enabled before attempting to initialize KMManager KMManager.initialize(getApplicationContext(), KeyboardType.KEYBOARD_TYPE_INAPP); KMManager.executeResourceUpdate(this); @@ -214,8 +209,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene DefaultLanguageResource.install(context); SharedPreferences prefs = getSharedPreferences(getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); - KMManager.SpacebarText spacebarText = KMManager.SpacebarText.fromString( - prefs.getString(KeymanSettingsKeys.SPACEBAR_TEXT, KMManager.SpacebarText.LANGUAGE_KEYBOARD.toString())); + KMManager.SpacebarText spacebarText = KMManager.SpacebarText.fromString(prefs.getString(KeymanSettingsKeys.SPACEBAR_TEXT, KMManager.SpacebarText.LANGUAGE_KEYBOARD.toString())); KMManager.setSpacebarText(spacebarText); checkHapticFeedback(); @@ -254,20 +248,12 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene toolbar = (Toolbar) findViewById(R.id.titlebar); setSupportActionBar(toolbar); getSupportActionBar().setTitle(null); - getSupportActionBar().setDisplayUseLogoEnabled(true); getSupportActionBar().setDisplayShowHomeEnabled(true); - getSupportActionBar().setDisplayShowTitleEnabled(true); getSupportActionBar().setLogo(R.drawable.keyman_logo); - - getSupportActionBar().setDisplayUseLogoEnabled(false); - getSupportActionBar().setDisplayShowHomeEnabled(false); - - getSupportActionBar().setDisplayUseLogoEnabled(true); - getSupportActionBar().setDisplayShowHomeEnabled(true); - getSupportActionBar().setLogo(R.drawable.keyman_logo_mode); - getSupportActionBar().setDisplayShowTitleEnabled(false); + // For Darkmode - once switched to dark mode, the logo becomes white + getSupportActionBar().setLogo(R.drawable.keyman_logo_mode); getSupportActionBar().setBackgroundDrawable(getActionBarDrawable(this)); drawerLayout = findViewById(R.id.drawer_layout); From 2b800fa3f24023ee470d2543ee2d2dd9158da842 Mon Sep 17 00:00:00 2001 From: Mengchou97 Date: Wed, 10 Jun 2026 16:18:33 +0700 Subject: [PATCH 056/140] removed whitespaces problems --- .../com/tavultesoft/kmapro/MainActivity.java | 177 +++++++++--------- 1 file changed, 84 insertions(+), 93 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index 0e64f5f285..b36da60da5 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -449,11 +449,9 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene // Reset keyboard picker Activity Task flag KMManager.closeParentAppOnShowKeyboardPicker(); - // onConfigurationChanged() only triggers when device is rotated while app is in - // foreground + // onConfigurationChanged() only triggers when device is rotated while app is in foreground // This handles when device is rotated while app is in background - // using KMManager.getOrientation() since getConfiguration().orientation is - // unreliable #10241 + // using KMManager.getOrientation() since getConfiguration().orientation is unreliable #10241 int newOrientation = KMManager.getOrientation(context); if (newOrientation != lastOrientation) { lastOrientation = newOrientation; @@ -498,17 +496,16 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene downloadKMP(loadingIntentUri, KmpInstallMode.Full); break; case "keyman" : - // TODO: Only accept download links from Keyman browser activities when - // universal links work + // TODO: Only accept download links from Keyman browser activities when universal links work KMLog.LogBreadcrumb(TAG, "Installing via keyman-link intent: " + link, true); if (KMPLink.isKeymanDownloadLink(loadingIntentUri.toString())) { // Convert opaque URI to hierarchical URI so the query parameters can be parsed Builder builder = new Uri.Builder(); builder.scheme("https") - .authority("keyman.com") - .appendPath("keyboards") - .encodedQuery(loadingIntentUri.getEncodedQuery()); + .authority("keyman.com") + .appendPath("keyboards") + .encodedQuery(loadingIntentUri.getEncodedQuery()); loadingIntentUri = Uri.parse(builder.build().toString()); downloadKMP(loadingIntentUri, KmpInstallMode.Full); } else if (KMPLink.isLegacyKeymanDownloadLink(loadingIntentUri.toString())) { @@ -519,7 +516,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene KMLog.LogError(TAG, msg); } break; - default: + default : String msg = "Unrecognized scheme: " + scheme; KMLog.LogError(TAG, msg); } @@ -574,7 +571,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene final ViewGroup _rootView = (ViewGroup) theItem.getActionView(); if(anUpdateCount==0) { - if(aHideMenuitem) { + if (aHideMenuitem) { theItem.setVisible(false); } else if (_rootView != null) { _rootView.findViewById(R.id.update_count_indicator).setVisibility(View.GONE); @@ -597,34 +594,42 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene @Override public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); this.menu = menu; - KMManager.getUpdateTool().addPropertyChangeListener(new PropertyChangeListener() { - @Override - public void propertyChange(PropertyChangeEvent evt) { - if (!evt.getPropertyName().equals("updateCount")) - return; - updateUpdateCountIndicator( - evt.getNewValue() == null ? 0 : (Integer) evt.getNewValue()); + KMManager.getUpdateTool().addPropertyChangeListener(new PropertyChangeListener() + { + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (!evt.getPropertyName().equals("updateCount")) + return; + updateUpdateCountIndicator( + evt.getNewValue() == null ? 0 : (Integer) evt.getNewValue()); + } } - }); + ); updateUpdateCountIndicator(KMManager.getUpdateTool().getOpenUpdateCount()); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { + // Android Gradle 8.0 no longer declares resources final, so can't use switch statement here if (item.getItemId() == R.id.action_info) { showInfo(); return true; } else if (item.getItemId() == R.id.action_share) { showShareDialog(); return true; + /* Disable Web Browser to investigate Google sign-in + } else if ((item.getItemId() == R.id.action_web) { + showWebBrowser(); + return true;*/ } else if (item.getItemId() == R.id.action_text_size) { showTextSizeDialog(); return true; - } else if (item.getItemId() == R.id.action_clear_text) { + } else if (item.getItemId() == R.id.action_clear_text){ showClearTextDialog(); return true; } else if (item.getItemId() == R.id.action_get_started) { @@ -635,6 +640,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene return true; } else if (item.getItemId() == R.id.action_update_keyboards) { KMManager.getUpdateTool().executeOpenUpdates(); + // Dismiss icon updateUpdateCountIndicator(0); final MenuItem _keyboardupdate = menu.findItem(R.id.action_update_keyboards); if (_keyboardupdate != null && _keyboardupdate.isVisible()) { @@ -833,39 +839,33 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } public void downloadKMP(String packageId, String bcp47, KmpInstallMode installMode) { - Uri downloadUri = bcp47 == null - ? Uri.parse(KMString.format("https://keyman.com/go/package/download/%s", new Object[] { packageId })) - : Uri.parse( - KMString.format("https://keyman.com/go/package/download/%s?bcp47=%s", new Object[] { packageId, bcp47 })); + Uri downloadUri = bcp47 == null ? + Uri.parse(KMString.format("https://keyman.com/go/package/download/%s", new Object[] { packageId })) : + Uri.parse(KMString.format("https://keyman.com/go/package/download/%s?bcp47=%s", new Object[] { packageId, bcp47 })); downloadKMP(downloadUri, installMode); } /** - * Parse the URI data to determine the filename and URL for the .kmp keyboard - * package. + * Parse the URI data to determine the filename and URL for the .kmp keyboard package. * If URL is valid, download the kmp. - * * @param packageUri URI to download the package. * @param installMode KMP installation mode (silent, welcome only, or full) - * TODO: only ever pass packageId and bcp47 from callers, as - * KMPLink should be responsible for - * URL parsing, not this function. + * TODO: only ever pass packageId and bcp47 from callers, as KMPLink should be responsible for + * URL parsing, not this function. */ public void downloadKMP(Uri packageUri, KmpInstallMode installMode) { if (packageUri == null) { - KMLog.LogError(TAG, "null uri passed to downloadKmp"); + KMLog.LogError(TAG,"null uri passed to downloadKmp"); String message = "Download failed. Invalid URL."; Toast.makeText(getApplicationContext(), message, - Toast.LENGTH_SHORT).show(); + Toast.LENGTH_SHORT).show(); return; } try { - // Initial try with Keyman 13.0 download link, format - // keyman://localhost/install?url=... + // Initial try with Keyman 13.0 download link, format keyman://localhost/install?url=... String url = packageUri.getQueryParameter(KMKeyboardDownloaderActivity.KMKey_URL); if (url == null) { - // Otherwise, Keyman 14+ download link is format - // https://keyman.com/go/package/download/[?bcp47=][&...] + // Otherwise, Keyman 14+ download link is format https://keyman.com/go/package/download/[?bcp47=][&...] url = packageUri.toString(); } if (url != null) { @@ -881,8 +881,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene // Parse query for the BCP 47 language ID String languageID = packageUri.getQueryParameter(KMKeyboardDownloaderActivity.KMKey_BCP47); - // TODO: Using "tag" for now, but production will be - // KMKeyboardDownloaderActivity.KMKey_BCP47 + // TODO: Using "tag" for now, but production will be KMKeyboardDownloaderActivity.KMKey_BCP47 if (languageID == null) { languageID = packageUri.getQueryParameter("tag"); } @@ -916,18 +915,18 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } catch (Exception e) { KMLog.LogException(TAG, "", e); cleanupPackageInstall(); - return;// break; + return;//break; } } else { String message = "Download failed. Not a .kmp keyboard package."; Toast.makeText(getApplicationContext(), message, - Toast.LENGTH_SHORT).show(); + Toast.LENGTH_SHORT).show(); } } catch (UnsupportedOperationException e) { String message = "Download failed. Invalid URL."; - KMLog.LogException(TAG, message, e); + KMLog.LogException(TAG, message, e); Toast.makeText(getApplicationContext(), message, - Toast.LENGTH_SHORT).show(); + Toast.LENGTH_SHORT).show(); cleanupPackageInstall(); } } @@ -947,8 +946,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene // Status bar height only used for portrait orientation int statusBarHeight = 0; if (lastOrientation == Configuration.ORIENTATION_PORTRAIT) { - // *** TO DO: Try to check if status bar is visible, set statusBarHeight to 0 if - // it is not visible *** + // *** TO DO: Try to check if status bar is visible, set statusBarHeight to 0 if it is not visible *** int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android"); if (resourceId > 0) { statusBarHeight = getResources().getDimensionPixelSize(resourceId); @@ -959,7 +957,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene Point size = KMManager.getWindowSize(context); int screenHeight = size.y; textView.setHeight( - screenHeight - statusBarHeight - actionBarHeight - bannerHeight - keyboardHeight - navigationBarHeight); + screenHeight - statusBarHeight - actionBarHeight - bannerHeight - keyboardHeight - navigationBarHeight); } private void showInfo() { @@ -982,10 +980,8 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene private void showShareDialog() { // Sharing text content via send intent - // Reference: - // https://developer.android.com/training/sharing/send#send-text-content - // Note: Sharing to Facebook removed in - // https://github.com/keymanapp/keyman/issues/156 + // Reference: https://developer.android.com/training/sharing/send#send-text-content + // Note: Sharing to Facebook removed in https://github.com/keymanapp/keyman/issues/156 // Users can copy text or use Keyman as system keyboard for typing into Facebook Intent sendIntent = new Intent(Intent.ACTION_SEND); sendIntent.setType("text/plain"); @@ -1060,10 +1056,10 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene /** * Combine a localized string for "Text Size" plus Arabic numerals - * * @return String */ private String getTextSizeString() { + // Instead of formatting the number, will truncate formatting and concat the actual textSize String label = getString(R.string.text_size).replace("%1$d", ""); return label + KMString.format(" %d", textSize); } @@ -1147,10 +1143,10 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } private void displayInstallWebView() { - TextView textView = (TextView) findViewById((R.id.kmWebViewChromeTextView)); + TextView textView = (TextView)findViewById((R.id.kmWebViewChromeTextView)); textView.setText(getString(R.string.body_install_webview)); - Button button = (Button) findViewById(R.id.webViewChromeButton); + Button button = (Button)findViewById(R.id.webViewChromeButton); button.setText(getString(R.string.label_install_webview)); button.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { @@ -1165,8 +1161,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene startActivity(intent); } } catch (android.content.ActivityNotFoundException e) { - Toast.makeText(getApplicationContext(), getString(R.string.unable_to_open_browser), Toast.LENGTH_SHORT) - .show(); + Toast.makeText(getApplicationContext(), getString(R.string.unable_to_open_browser), Toast.LENGTH_SHORT).show(); } } }); @@ -1176,16 +1171,16 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } private void displayEnableWebView() { - TextView textView = (TextView) findViewById((R.id.kmWebViewChromeTextView)); + TextView textView = (TextView)findViewById((R.id.kmWebViewChromeTextView)); textView.setText(getString(R.string.body_enable_webview)); - Button button = (Button) findViewById(R.id.webViewChromeButton); + Button button = (Button)findViewById(R.id.webViewChromeButton); button.setText(getString(R.string.label_enable_webview)); button.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { // Application settings to enable WebView Intent intent = new Intent("android.settings.APPLICATION_DETAILS_SETTINGS", - Uri.parse("package:com.google.android.webview")); + Uri.parse("package:com.google.android.webview")); // Launch intent try { @@ -1194,8 +1189,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene startActivity(intent); } } catch (android.content.ActivityNotFoundException e) { - Toast.makeText(getApplicationContext(), getString(R.string.unable_to_open_browser), Toast.LENGTH_SHORT) - .show(); + Toast.makeText(getApplicationContext(), getString(R.string.unable_to_open_browser), Toast.LENGTH_SHORT).show(); } } }); @@ -1206,17 +1200,17 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene private void displayUpdateChrome() { // TextView's default string is to update Chrome - TextView textView = (TextView) findViewById(R.id.kmWebViewChromeTextView); + TextView textView = (TextView)findViewById(R.id.kmWebViewChromeTextView); textView.setText(String.format(getString(R.string.text_require_chrome_version), WebViewUtils.KEYMAN_MIN_TARGET_VERSION_ANDROID_CHROME)); - Button button = (Button) findViewById(R.id.webViewChromeButton); + Button button = (Button)findViewById(R.id.webViewChromeButton); button.setText(getString(R.string.button_update_chrome)); button.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { // Primary Play Store link to install/update Chrome Intent intent = new Intent(Intent.ACTION_VIEW, - Uri.parse("market://details?id=com.android.chrome")); + Uri.parse("market://details?id=com.android.chrome")); try { PackageManager packageManager = context.getPackageManager(); @@ -1225,14 +1219,13 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } else { // (alternate Play Store link to install/update Chrome) intent = new Intent(Intent.ACTION_VIEW, - Uri.parse("https://play.google.com/store/apps/details?id=com.android.chrome")); + Uri.parse("https://play.google.com/store/apps/details?id=com.android.chrome")); intent.resolveActivity(packageManager); startActivity(intent); } } catch (android.content.ActivityNotFoundException e) { - Toast.makeText(getApplicationContext(), getString(R.string.unable_to_open_browser), Toast.LENGTH_SHORT) - .show(); + Toast.makeText(getApplicationContext(), getString(R.string.unable_to_open_browser), Toast.LENGTH_SHORT).show(); } } }); @@ -1279,8 +1272,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene return; } else if (!isKMP) { String noKeyboardsInstalledMessage = String.format( - context.getString(R.string.not_valid_package_file), filename, - context.getString(R.string.no_targets_to_install)); + context.getString(R.string.not_valid_package_file), filename, context.getString(R.string.no_targets_to_install)); Toast.makeText(context, noKeyboardsInstalledMessage, Toast.LENGTH_LONG).show(); return; } @@ -1761,8 +1753,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene public static void cleanupPackageInstall() { if (progressDialog != null && progressDialog.isShowing()) { progressDialog.dismiss(); - } - + }; progressDialog = null; } @@ -1820,21 +1811,21 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene HashMap hashMap = new HashMap<>(keyboardsInstalled.get(i)); String languageID = hashMap.get(KMManager.KMKey_LanguageID); Keyboard keyboardInfo = new Keyboard( - hashMap.get(KMManager.KMKey_PackageID), - hashMap.get(KMManager.KMKey_KeyboardID), - hashMap.get(KMManager.KMKey_KeyboardName), - languageID, - hashMap.get(KMManager.KMKey_LanguageName), - hashMap.get(KMManager.KMKey_Version), - hashMap.get(KMManager.KMKey_CustomHelpLink), - hashMap.get(KMManager.KMKey_KMPLink), - true, - hashMap.get(KMManager.KMKey_Font), - hashMap.get(KMManager.KMKey_OskFont)); + hashMap.get(KMManager.KMKey_PackageID), + hashMap.get(KMManager.KMKey_KeyboardID), + hashMap.get(KMManager.KMKey_KeyboardName), + languageID, + hashMap.get(KMManager.KMKey_LanguageName), + hashMap.get(KMManager.KMKey_Version), + hashMap.get(KMManager.KMKey_CustomHelpLink), + hashMap.get(KMManager.KMKey_KMPLink), + true, + hashMap.get(KMManager.KMKey_Font), + hashMap.get(KMManager.KMKey_OskFont)); - if (hashMap.containsKey(KMManager.KMKey_KMPInstall_Mode)) { - installMode = KmpInstallMode.fromString(hashMap.get(KMManager.KMKey_KMPInstall_Mode)); - } + if (hashMap.containsKey(KMManager.KMKey_KMPInstall_Mode)) { + installMode = KmpInstallMode.fromString(hashMap.get(KMManager.KMKey_KMPInstall_Mode)); + } if (i == 0) { packageID = keyboardInfo.getPackageID(); customHelpLink = keyboardInfo.getHelpLink(); @@ -1862,11 +1853,11 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene ArrayList aPreparedCloudApiParams = new ArrayList<>(); String url = CloudRepository.prepareLexicalModelQuery(languageID); aPreparedCloudApiParams.add(new CloudApiTypes.CloudApiParam( - CloudApiTypes.ApiTarget.KeyboardLexicalModels, url).setType(CloudApiTypes.JSONType.Array)); + CloudApiTypes.ApiTarget.KeyboardLexicalModels, url).setType(CloudApiTypes.JSONType.Array)); CloudDownloadMgr.getInstance().executeAsDownload( - context, _downloadid, null, _callback, - aPreparedCloudApiParams.toArray(new CloudApiTypes.CloudApiParam[0])); + context, _downloadid, null, _callback, + aPreparedCloudApiParams.toArray(new CloudApiTypes.CloudApiParam[0])); } } } @@ -1888,14 +1879,14 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene public void onLexicalModelInstalled(List> lexicalModelsInstalled) { cleanupPackageInstall(); - String langId = (KMManager.getCurrentKeyboardInfo(this) != null) - ? KMManager.getCurrentKeyboardInfo(this).getLanguageID() - : KMManager.KMDefault_LanguageID; + String langId = (KMManager.getCurrentKeyboardInfo(this) != null) ? + KMManager.getCurrentKeyboardInfo(this).getLanguageID() : + KMManager.KMDefault_LanguageID; boolean matchingModel = false; - for (int i = 0; i < lexicalModelsInstalled.size(); i++) { - HashMap lexicalModelInfo = new HashMap<>(lexicalModelsInstalled.get(i)); - if (lexicalModelInfo.get(KMManager.KMKey_LanguageID).equals(langId)) { + for(int i=0; ilexicalModelInfo = new HashMap<>(lexicalModelsInstalled.get(i)); + if(lexicalModelInfo.get(KMManager.KMKey_LanguageID).equals(langId)) { matchingModel = true; } KMManager.addLexicalModel(this, lexicalModelInfo); @@ -1903,7 +1894,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene // We're on the main thread, so if the active keyboard's language code matches, // let's register the associated lexical model. - if (matchingModel) { + if(matchingModel) { KMManager.registerAssociatedLexicalModel(langId); } } From 536b5b5c61906ccff1a19679fb3dfed54fa9ea87 Mon Sep 17 00:00:00 2001 From: Mengchou97 Date: Wed, 10 Jun 2026 16:49:42 +0700 Subject: [PATCH 057/140] fixed more whitespace problems --- android/KMAPro/kMAPro/src/main/AndroidManifest.xml | 2 +- .../kMAPro/src/main/res/drawable/ic_action_timelapse.xml | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/AndroidManifest.xml b/android/KMAPro/kMAPro/src/main/AndroidManifest.xml index 27488ca166..5de97baa87 100644 --- a/android/KMAPro/kMAPro/src/main/AndroidManifest.xml +++ b/android/KMAPro/kMAPro/src/main/AndroidManifest.xml @@ -66,7 +66,7 @@ android:exported="true" android:label="@string/app_name" android:resizeableActivity="false" - android:theme="@style/Theme.App.Starting"> + android:theme="@style/Theme.App.Starting"> diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_timelapse.xml b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_timelapse.xml index c3f9cfdcc8..4de4c986db 100644 --- a/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_timelapse.xml +++ b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_timelapse.xml @@ -1,8 +1,3 @@ - - - - - - + \ No newline at end of file From 2f1f62aed4d9eb54388b5f673c2f14dbdad10d3a Mon Sep 17 00:00:00 2001 From: Mengchou97 Date: Wed, 10 Jun 2026 16:53:14 +0700 Subject: [PATCH 058/140] fixed whitespace problems fragment_select_language --- .../res/layout/fragment_select_language.xml | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/res/layout/fragment_select_language.xml b/android/KMAPro/kMAPro/src/main/res/layout/fragment_select_language.xml index dfa5d5f532..e06f78593b 100644 --- a/android/KMAPro/kMAPro/src/main/res/layout/fragment_select_language.xml +++ b/android/KMAPro/kMAPro/src/main/res/layout/fragment_select_language.xml @@ -1,14 +1,15 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" - + tools:context=".SelectLanguageFragment" > + + From 2b5430225725f2372edeb5c04a0ea5b51c2cead7 Mon Sep 17 00:00:00 2001 From: Mengchou97 Date: Wed, 10 Jun 2026 16:55:49 +0700 Subject: [PATCH 059/140] reverted the ic_action_timelapse --- .../kMAPro/src/main/res/drawable/ic_action_timelapse.xml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_timelapse.xml b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_timelapse.xml index 4de4c986db..c3f9cfdcc8 100644 --- a/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_timelapse.xml +++ b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_timelapse.xml @@ -1,3 +1,8 @@ + - \ No newline at end of file + + + + + From d1e7839c08dbc9573353561981be9dd5d6c2433c Mon Sep 17 00:00:00 2001 From: Mengchou97 Date: Wed, 10 Jun 2026 17:00:17 +0700 Subject: [PATCH 060/140] reverted the splashscreenactivity --- .../kmapro/SplashScreenActivity.java | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/SplashScreenActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/SplashScreenActivity.java index ce2fc7ed6d..60d26d7508 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/SplashScreenActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/SplashScreenActivity.java @@ -1,20 +1,22 @@ package com.tavultesoft.kmapro; -import android.content.SharedPreferences; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.Bundle; import android.os.Handler; +import android.app.Activity; import android.content.Intent; import android.widget.TextView; -import androidx.appcompat.app.AppCompatActivity; +import com.keyman.engine.util.KMLog; + import java.util.Calendar; -public class SplashScreenActivity extends AppCompatActivity { +public class SplashScreenActivity extends Activity { private final static String TAG = "Splash"; + // For now, disable this Activity so app is not delayed showing version/copyright text - private static final int SPLASH_TIME_OUT = 0; // msec + private static int SPLASH_TIME_OUT = 0; // msec @Override protected void onCreate(Bundle savedInstanceState) { @@ -22,10 +24,13 @@ public class SplashScreenActivity extends AppCompatActivity { setContentView(R.layout.activity_splash_screen); TextView version = (TextView)findViewById(R.id.splash_version); + String ver = ""; + PackageInfo pInfo; try { - PackageInfo pInfo = getPackageManager().getPackageInfo(getPackageName(), 0); - String ver = String.format("%s: %s", version.getText(), pInfo.versionName); + pInfo = getPackageManager().getPackageInfo(getPackageName(), 0); + ver = String.format("%s: %s", version.getText(), pInfo.versionName); } catch (PackageManager.NameNotFoundException e) { + KMLog.LogException(TAG, "", e); // Could not get version number } // TODO: Add version and copyright info when Android supports TextViews in the splash screen From 9dfc7b096c0d759f21f9304326c3410a78c34a54 Mon Sep 17 00:00:00 2001 From: Mengchou97 Date: Thu, 11 Jun 2026 13:57:34 +0700 Subject: [PATCH 061/140] added the standard header --- .../java/com/tavultesoft/kmapro/KeymanApplication.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanApplication.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanApplication.java index e3a3094eb6..3fb3588f5a 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanApplication.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanApplication.java @@ -1,5 +1,11 @@ -/** - * Copyright (C) SIL International. All rights reserved. +/* + * Keyman is copyright (C) SIL Global. MIT License. + * + * Created by MengchouChey on 2026-06-10 + * Internship Program, 9th Feb - 27th Apr 2026 + * + * This file is the Application class for Keyman, which is used to set the default night mode based on user preferences when the application starts. + * */ package com.tavultesoft.kmapro; From 0df88615a2f9e7fcf2fd83726ed081f39825eecd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 14 Jun 2026 01:04:48 +0000 Subject: [PATCH 062/140] chore(deps-dev): bump esbuild from 0.25.0 to 0.28.1 Bumps [esbuild](https://github.com/evanw/esbuild) from 0.25.0 to 0.28.1. - [Release notes](https://github.com/evanw/esbuild/releases) - [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG-2025.md) - [Commits](https://github.com/evanw/esbuild/compare/v0.25.0...v0.28.1) --- updated-dependencies: - dependency-name: esbuild dependency-version: 0.28.1 dependency-type: direct:development ... Signed-off-by: dependabot[bot] --- developer/src/kmc-model/package.json | 2 +- developer/src/kmc/package.json | 2 +- package-lock.json | 1870 ++++++++++++++++++++------ package.json | 2 +- 4 files changed, 1431 insertions(+), 445 deletions(-) diff --git a/developer/src/kmc-model/package.json b/developer/src/kmc-model/package.json index 52ef40c425..3a3bafdb6c 100644 --- a/developer/src/kmc-model/package.json +++ b/developer/src/kmc-model/package.json @@ -39,7 +39,7 @@ "@keymanapp/models-templates": "*", "c8": "^7.12.0", "chalk": "^2.4.2", - "esbuild": "^0.25.0" + "esbuild": "^0.28.1" }, "mocha": { "spec": "build/test/**/*.tests.js", diff --git a/developer/src/kmc/package.json b/developer/src/kmc/package.json index 4f15f5e98c..bd6c22159a 100644 --- a/developer/src/kmc/package.json +++ b/developer/src/kmc/package.json @@ -54,7 +54,7 @@ "devDependencies": { "@sentry/cli": "^2.31.0", "c8": "^7.12.0", - "esbuild": "^0.25.0", + "esbuild": "^0.28.1", "typescript": "^5.4.5" }, "mocha": { diff --git a/package-lock.json b/package-lock.json index 7871a01886..6a2b1af055 100644 --- a/package-lock.json +++ b/package-lock.json @@ -58,7 +58,7 @@ "ajv-cli": "^5.0.0", "ajv-formats": "^2.1.1", "chai": "^5.1.0", - "esbuild": "^0.25.0", + "esbuild": "^0.28.1", "eslint": "^8.39.0", "eslint-config-love": "^53.0.0", "eslint-plugin-import": "^2.27.5", @@ -341,7 +341,7 @@ "devDependencies": { "@sentry/cli": "^2.31.0", "c8": "^7.12.0", - "esbuild": "^0.25.0", + "esbuild": "^0.28.1", "typescript": "^5.4.5" } }, @@ -872,7 +872,7 @@ "@keymanapp/models-templates": "*", "c8": "^7.12.0", "chalk": "^2.4.2", - "esbuild": "^0.25.0" + "esbuild": "^0.28.1" } }, "developer/src/kmc-model-info": { @@ -1394,10 +1394,299 @@ "dev": true, "license": "MIT" }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz", - "integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", "cpu": [ "arm64" ], @@ -1411,6 +1700,142 @@ "node": ">=18" } }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -4305,6 +4730,490 @@ "node": ">=18.0.0" } }, + "node_modules/@web/dev-server-esbuild/node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@web/dev-server-esbuild/node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@web/dev-server-esbuild/node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@web/dev-server-esbuild/node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@web/dev-server-esbuild/node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@web/dev-server-esbuild/node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@web/dev-server-esbuild/node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@web/dev-server-esbuild/node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@web/dev-server-esbuild/node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@web/dev-server-esbuild/node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@web/dev-server-esbuild/node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@web/dev-server-esbuild/node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@web/dev-server-esbuild/node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@web/dev-server-esbuild/node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@web/dev-server-esbuild/node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@web/dev-server-esbuild/node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@web/dev-server-esbuild/node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@web/dev-server-esbuild/node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@web/dev-server-esbuild/node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@web/dev-server-esbuild/node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@web/dev-server-esbuild/node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@web/dev-server-esbuild/node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@web/dev-server-esbuild/node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@web/dev-server-esbuild/node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@web/dev-server-esbuild/node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@web/dev-server-esbuild/node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@web/dev-server-esbuild/node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, "node_modules/@web/dev-server-esbuild/node_modules/parse5": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", @@ -6679,9 +7588,9 @@ "license": "MIT" }, "node_modules/esbuild": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz", - "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -6692,439 +7601,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.0", - "@esbuild/android-arm": "0.25.0", - "@esbuild/android-arm64": "0.25.0", - "@esbuild/android-x64": "0.25.0", - "@esbuild/darwin-arm64": "0.25.0", - "@esbuild/darwin-x64": "0.25.0", - "@esbuild/freebsd-arm64": "0.25.0", - "@esbuild/freebsd-x64": "0.25.0", - "@esbuild/linux-arm": "0.25.0", - "@esbuild/linux-arm64": "0.25.0", - "@esbuild/linux-ia32": "0.25.0", - "@esbuild/linux-loong64": "0.25.0", - "@esbuild/linux-mips64el": "0.25.0", - "@esbuild/linux-ppc64": "0.25.0", - "@esbuild/linux-riscv64": "0.25.0", - "@esbuild/linux-s390x": "0.25.0", - "@esbuild/linux-x64": "0.25.0", - "@esbuild/netbsd-arm64": "0.25.0", - "@esbuild/netbsd-x64": "0.25.0", - "@esbuild/openbsd-arm64": "0.25.0", - "@esbuild/openbsd-x64": "0.25.0", - "@esbuild/sunos-x64": "0.25.0", - "@esbuild/win32-arm64": "0.25.0", - "@esbuild/win32-ia32": "0.25.0", - "@esbuild/win32-x64": "0.25.0" - } - }, - "node_modules/esbuild/node_modules/@esbuild/aix-ppc64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz", - "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/android-arm": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz", - "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/android-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz", - "integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/android-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz", - "integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/darwin-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz", - "integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/darwin-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz", - "integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz", - "integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/freebsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz", - "integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/linux-arm": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz", - "integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/linux-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz", - "integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/linux-ia32": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz", - "integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/linux-loong64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz", - "integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/linux-mips64el": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz", - "integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/linux-ppc64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz", - "integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/linux-riscv64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz", - "integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/linux-s390x": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz", - "integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/linux-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz", - "integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/netbsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz", - "integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz", - "integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/openbsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz", - "integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/sunos-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz", - "integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/win32-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz", - "integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/win32-ia32": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz", - "integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/win32-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz", - "integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" } }, "node_modules/escalade": { @@ -13141,6 +13643,490 @@ "fsevents": "~2.3.3" } }, + "node_modules/tsx/node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, "node_modules/tsx/node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", diff --git a/package.json b/package.json index 9982c65a33..bee0177980 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "ajv-cli": "^5.0.0", "ajv-formats": "^2.1.1", "chai": "^5.1.0", - "esbuild": "^0.25.0", + "esbuild": "^0.28.1", "eslint": "^8.39.0", "eslint-config-love": "^53.0.0", "eslint-plugin-import": "^2.27.5", From a9933abf479359a3825e1d391cadc2c0fb403ee9 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Tue, 16 Jun 2026 10:18:46 +1000 Subject: [PATCH 063/140] feat(windows): add network connectivity tool This adds a helper unit which uses the windows API to obtain a ConnectionProfile. This profile provides information about the connection status and connectivity statistics. This includes the connection costs, roaming, restricted etc. It also can check to see if background networking activity has been restricted. Using the recommened example this commit combines these to determine if a network is metered. It also provides access to the background network being restricted. Fixes: #13566 --- windows/src/desktop/kmshell/kmshell.dpr | 3 +- windows/src/desktop/kmshell/kmshell.dproj | 19 ++-- ...yman.Configuration.UI.UfrmStartInstall.dfm | 30 +++++- ...yman.Configuration.UI.UfrmStartInstall.pas | 14 ++- .../main/Keyman.System.UpdateStateMachine.pas | 12 ++- windows/src/desktop/kmshell/main/UfrmMain.pas | 8 +- .../kmshell/util/UtilNetworkConnection.pas | 94 +++++++++++++++++++ windows/src/desktop/kmshell/xml/strings.xml | 7 +- 8 files changed, 167 insertions(+), 20 deletions(-) create mode 100644 windows/src/desktop/kmshell/util/UtilNetworkConnection.pas diff --git a/windows/src/desktop/kmshell/kmshell.dpr b/windows/src/desktop/kmshell/kmshell.dpr index 0f6bf22dd5..214c8c86a3 100644 --- a/windows/src/desktop/kmshell/kmshell.dpr +++ b/windows/src/desktop/kmshell/kmshell.dpr @@ -182,7 +182,8 @@ uses Keyman.System.UpdateStateMachine in 'main\Keyman.System.UpdateStateMachine.pas', Keyman.System.DownloadUpdate in 'main\Keyman.System.DownloadUpdate.pas', Keyman.System.ExecutionHistory in '..\..\..\..\common\windows\delphi\general\Keyman.System.ExecutionHistory.pas', - Keyman.Configuration.UI.UfrmStartInstall in 'main\Keyman.Configuration.UI.UfrmStartInstall.pas' {frmStartInstall}; + Keyman.Configuration.UI.UfrmStartInstall in 'main\Keyman.Configuration.UI.UfrmStartInstall.pas' {frmStartInstall}, + UtilNetworkConnection in 'util\UtilNetworkConnection.pas'; {$R VERSION.RES} {$R manifest.res} diff --git a/windows/src/desktop/kmshell/kmshell.dproj b/windows/src/desktop/kmshell/kmshell.dproj index ec44fc98ec..fbe3df5702 100644 --- a/windows/src/desktop/kmshell/kmshell.dproj +++ b/windows/src/desktop/kmshell/kmshell.dproj @@ -357,6 +357,7 @@
frmStartInstall
+ Cfg_2 @@ -418,18 +419,6 @@ False - - - kmshell.rsm - true - - - - - kmshell.exe - true - - kmshell.exe @@ -442,6 +431,12 @@ true + + + kmshell.rsm + true + + 1 diff --git a/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.dfm b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.dfm index 2f44143138..9bd3818631 100644 --- a/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.dfm +++ b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.dfm @@ -20,7 +20,7 @@ object frmStartInstall: TfrmStartInstall object lblUpdateMessage: TLabel Left = 64 Top = 89 - Width = 313 + Width = 303 Height = 34 Caption = 'An update to Keyman has been downloaded and is ready to install.' Font.Charset = DEFAULT_CHARSET @@ -146,6 +146,34 @@ object frmStartInstall: TfrmStartInstall 414B8B49C3A0A5C5A461D0D262FA3F82D7F60E256D51F10000000049454E44AE 426082} end + object shpMeteredWarning: TShape + Left = 60 + Top = 149 + Width = 314 + Height = 45 + Brush.Color = 15132415 + Pen.Color = clRed + Visible = False + end + object lblMeteredWarning: TLabel + Left = 64 + Top = 150 + Width = 303 + Height = 43 + AutoSize = False + Caption = 'Metered Warning' + Color = 15132415 + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -13 + Font.Name = 'Segoe UI' + Font.Style = [] + ParentColor = False + ParentFont = False + Transparent = False + Visible = False + WordWrap = True + end object cmdInstall: TButton Left = 229 Top = 200 diff --git a/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas index be1dbe31aa..dcf5bf0f3f 100644 --- a/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas +++ b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas @@ -19,7 +19,9 @@ uses Winapi.Messages, Winapi.Windows, UfrmKeymanBase, - UserMessages, Vcl.Imaging.pngimage; + UserMessages, + UtilNetworkConnection, + Vcl.Imaging.pngimage; type TfrmStartInstall = class(TfrmKeymanBase) @@ -27,6 +29,8 @@ type cmdLater: TButton; lblUpdateMessage: TLabel; imgKeymanLogo: TImage; + shpMeteredWarning: TShape; + lblMeteredWarning: TLabel; procedure FormCreate(Sender: TObject); private FRestartRequired: Boolean; @@ -48,6 +52,8 @@ begin end; procedure TfrmStartInstall.FormCreate(Sender: TObject); +var + IsMetered: Boolean; begin inherited; cmdInstall.Caption := MsgFromId(S_Update_Now); @@ -56,6 +62,12 @@ begin lblUpdateMessage.Caption := MsgFromId(S_Update_Restart_Req) else lblUpdateMessage.Caption := MsgFromId(S_Ready_To_Install); + + IsMetered := UtilNetworkConnection.IsMetered; + lblMeteredWarning.Visible := IsMetered; + shpMeteredWarning.Visible := IsMetered; + if IsMetered then + lblMeteredWarning.Caption := MsgFromId(S_Metered_Warning); end; end. diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index c50ecf14db..b6209fcf2c 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -16,7 +16,8 @@ uses KeymanPaths, Keyman.System.ExecutionHistory, Keyman.System.UpdateCheckResponse, - utilkmshell; + utilkmshell, + UtilNetworkConnection; type EUpdateStateMachine = class(Exception); @@ -719,7 +720,9 @@ procedure UpdateAvailableState.EnterState; begin // Enter UpdateAvailableState bucStateContext.SetRegistryState(usUpdateAvailable); - if bucStateContext.FAutomaticUpdate then + + if bucStateContext.FAutomaticUpdate and + not UtilNetworkConnection.IsBackgroundUpdateAllowed then begin StartDownloadProcess; end; @@ -751,7 +754,8 @@ end; function UpdateAvailableState.HandleKmShell; begin - if bucStateContext.FAutomaticUpdate then + if bucStateContext.FAutomaticUpdate and + not UtilNetworkConnection.IsBackgroundUpdateAllowed then begin // we will use a new kmshell process to enable // the download as background process. @@ -773,6 +777,8 @@ end; procedure UpdateAvailableState.HandleInstallNow; begin + // This is deliberate action therefore no + // need to check if background update is allowed. bucStateContext.SetApplyNow(True); // A new kmshell process will be used to download StartDownloadProcess; diff --git a/windows/src/desktop/kmshell/main/UfrmMain.pas b/windows/src/desktop/kmshell/main/UfrmMain.pas index 6a631fb546..cbf4032bc5 100644 --- a/windows/src/desktop/kmshell/main/UfrmMain.pas +++ b/windows/src/desktop/kmshell/main/UfrmMain.pas @@ -212,6 +212,7 @@ uses utilexecute, utilkmshell, utilhttp, + UtilNetworkConnection, utiluac, utilxml, KeymanPaths; @@ -817,10 +818,15 @@ var ShellPath : string; FResult, InstallNow: Boolean; frmStartInstallNow: TfrmStartInstall; + IsMetered: Boolean; begin InstallNow := True; // Confirm User is ok that this will require a reset - if HasKeymanRun then + // The metered warning has been added to the update pop up. In the future + // it would be better to have this warning in a banner on the configuration + // page. + IsMetered := UtilNetworkConnection.IsMetered; + if HasKeymanRun OR IsMetered then begin frmStartInstallNow := TfrmStartInstall.Create(nil, true); try diff --git a/windows/src/desktop/kmshell/util/UtilNetworkConnection.pas b/windows/src/desktop/kmshell/util/UtilNetworkConnection.pas new file mode 100644 index 0000000000..e5ade9826e --- /dev/null +++ b/windows/src/desktop/kmshell/util/UtilNetworkConnection.pas @@ -0,0 +1,94 @@ +(* + * Keyman is copyright (C) SIL Global. MIT License. + * + * Notes: Enable checking for metered connection and background data restrictions. +*) +unit UtilNetworkConnection; + +interface + +(** + * Checks if the current internet connection is restricted, roaming, or over its + * data limit. + * This learn microsoft article shows how to combine network costs to determine + * if the connection is metered. + * https://learn.microsoft.com/en-us/uwp/api/windows.networking.connectivity.connectionprofile?view=winrt-28000 + * + * @returns True if the connection is metered, False otherwise. + *) +function IsMetered: Boolean; + +(** + * Checks if background data usage is explicitly restricted by the current network profile. + * + * @returns True if background data usage is restricted, False otherwise. + *) +function IsBackgroundDataRestricted: Boolean; + +(** + * Determines whether background updates are allowed. + * + * @returns True if updates can proceed, False if blocked by network constraints. + * + * Note: Currently this checks for metered connection OR background + data usage restricted. If a configuration item is added that + provides the option to download on metered connections then + this should be updated to include that logic + *) +function IsBackgroundUpdateAllowed: Boolean; + +implementation + +uses + System.SysUtils, + Winapi.CommonTypes, + Winapi.WinRT, + Winapi.Networking.Connectivity; + +function IsMetered: Boolean; +var + Profile: IConnectionProfile; + CostLevel: IConnectionCost; +begin + Result := False; + // Get the profile currently providing internet access + Profile := TNetworkInformation.GetInternetConnectionProfile; + + if Profile <> nil then + begin + CostLevel := Profile.GetConnectionCost; + Result := (CostLevel.NetworkCostType <> NetworkCostType.Unrestricted) + or CostLevel.Roaming + or CostLevel.OverDataLimit; + end; +end; + +function IsBackgroundDataRestricted: Boolean; +var + Profile: IConnectionProfile; + CostLevel: IConnectionCost; + DataRestriction: IConnectionCost2; +begin + Result := False; + Profile := TNetworkInformation.GetInternetConnectionProfile; + if Profile <> nil then + begin + CostLevel := Profile.GetConnectionCost; + if (CostLevel <> nil) and Supports(CostLevel, IConnectionCost2, DataRestriction) then + begin + Result := DataRestriction.BackgroundDataUsageRestricted; + Exit; + end; + end; +end; + +// Currently this checks for metered connection OR background +// data usage restricted. If a configuration item is added that +// provides the option to download on metered connections then +// this should be updated to include that logic +function IsBackgroundUpdateAllowed: Boolean; +begin + Result := IsMetered OR IsBackgroundDataRestricted; +end; + +end. diff --git a/windows/src/desktop/kmshell/xml/strings.xml b/windows/src/desktop/kmshell/xml/strings.xml index 4928761448..318b33f483 100644 --- a/windows/src/desktop/kmshell/xml/strings.xml +++ b/windows/src/desktop/kmshell/xml/strings.xml @@ -608,11 +608,16 @@ Installing the update now will require a restart of your computer before you can use Keyman again. Update now anyway? - + An update to Keyman has been downloaded and is ready to install. + + + + You\'re on a metered connection. Downloading now may incur data charges. + From 94756fd850671145fffaf9fcf419d510bfde803c Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Wed, 17 Jun 2026 11:41:09 +1000 Subject: [PATCH 064/140] feat(windows): add intstall ready flag to pop up If the download has already occured before calling the pop up then there is no reason to warn about the metered connection. --- .../Keyman.Configuration.UI.UfrmStartInstall.pas | 16 ++++++++++++---- windows/src/desktop/kmshell/main/initprog.pas | 2 +- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas index dcf5bf0f3f..e5b25c2052 100644 --- a/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas +++ b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas @@ -34,8 +34,12 @@ type procedure FormCreate(Sender: TObject); private FRestartRequired: Boolean; + FReadyToInstall: Boolean; public - constructor Create(AOwner: TComponent; const RestartRequired: Boolean); reintroduce; + constructor Create( + AOwner: TComponent; + const RestartRequired: Boolean; + const ReadyToInstall: Boolean = False); reintroduce; end; implementation @@ -45,10 +49,14 @@ uses {$R *.dfm} -constructor TfrmStartInstall.Create(AOwner: TComponent; const RestartRequired: Boolean); +constructor TfrmStartInstall.Create( + AOwner: TComponent; + const RestartRequired: Boolean; + const ReadyToInstall: Boolean = False); begin inherited Create(AOwner); FRestartRequired := RestartRequired; + FReadyToInstall := ReadyToInstall; end; procedure TfrmStartInstall.FormCreate(Sender: TObject); @@ -64,8 +72,8 @@ begin lblUpdateMessage.Caption := MsgFromId(S_Ready_To_Install); IsMetered := UtilNetworkConnection.IsMetered; - lblMeteredWarning.Visible := IsMetered; - shpMeteredWarning.Visible := IsMetered; + lblMeteredWarning.Visible := IsMetered and not FReadyToInstall; + shpMeteredWarning.Visible := IsMetered and not FReadyToInstall; if IsMetered then lblMeteredWarning.Caption := MsgFromId(S_Metered_Warning); end; diff --git a/windows/src/desktop/kmshell/main/initprog.pas b/windows/src/desktop/kmshell/main/initprog.pas index 359130f27e..81c2fc7c82 100644 --- a/windows/src/desktop/kmshell/main/initprog.pas +++ b/windows/src/desktop/kmshell/main/initprog.pas @@ -677,7 +677,7 @@ begin (FMode in [fmStart, fmSplash, fmMain, fmAbout, fmHelp, fmShowHelp, fmSettings, fmBoot]) then begin - frmStartInstall := TfrmStartInstall.Create(nil, false); + frmStartInstall := TfrmStartInstall.Create(nil, false, ValidateReadyToInstall); try Result := frmStartInstall.ShowModal = mrOk; finally From 293f8ec47a2480d01771562a19b5fca7f1ea5257 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Tue, 23 Jun 2026 11:41:02 +1000 Subject: [PATCH 065/140] feat(windows): apply suggestions from code review Co-authored-by: Eberhard Beilharz --- .../kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas | 2 ++ 1 file changed, 2 insertions(+) diff --git a/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas index e5b25c2052..f9a6ec630f 100644 --- a/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas +++ b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas @@ -72,6 +72,8 @@ begin lblUpdateMessage.Caption := MsgFromId(S_Ready_To_Install); IsMetered := UtilNetworkConnection.IsMetered; + // Show warning if on a metered connection. If FReadyToInstall the update is + // already downloaded, so no use in displaying the warning. lblMeteredWarning.Visible := IsMetered and not FReadyToInstall; shpMeteredWarning.Visible := IsMetered and not FReadyToInstall; if IsMetered then From 5e26d5dd08a609f39449ca244b0016dd1cbb31f4 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Tue, 23 Jun 2026 15:56:34 +1000 Subject: [PATCH 066/140] feat(windows): address review comments --- .../Keyman.Configuration.UI.UfrmStartInstall.pas | 16 ++++++++-------- .../main/Keyman.System.UpdateStateMachine.pas | 4 ++-- windows/src/desktop/kmshell/main/UfrmMain.pas | 10 +++++----- windows/src/desktop/kmshell/main/initprog.pas | 5 ++++- .../kmshell/util/UtilNetworkConnection.pas | 8 ++++---- 5 files changed, 23 insertions(+), 20 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas index f9a6ec630f..28e456f691 100644 --- a/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas +++ b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas @@ -20,7 +20,6 @@ uses Winapi.Windows, UfrmKeymanBase, UserMessages, - UtilNetworkConnection, Vcl.Imaging.pngimage; type @@ -34,11 +33,13 @@ type procedure FormCreate(Sender: TObject); private FRestartRequired: Boolean; + FIsMetered: Boolean; FReadyToInstall: Boolean; public constructor Create( AOwner: TComponent; const RestartRequired: Boolean; + const IsMetered: Boolean; const ReadyToInstall: Boolean = False); reintroduce; end; @@ -52,16 +53,16 @@ uses constructor TfrmStartInstall.Create( AOwner: TComponent; const RestartRequired: Boolean; + const IsMetered: Boolean; const ReadyToInstall: Boolean = False); begin inherited Create(AOwner); FRestartRequired := RestartRequired; + FIsMetered := IsMetered; FReadyToInstall := ReadyToInstall; end; procedure TfrmStartInstall.FormCreate(Sender: TObject); -var - IsMetered: Boolean; begin inherited; cmdInstall.Caption := MsgFromId(S_Update_Now); @@ -71,12 +72,11 @@ begin else lblUpdateMessage.Caption := MsgFromId(S_Ready_To_Install); - IsMetered := UtilNetworkConnection.IsMetered; - // Show warning if on a metered connection. If FReadyToInstall the update is + // Show warning if on a metered connection. If FReadyToInstall the update is // already downloaded, so no use in displaying the warning. - lblMeteredWarning.Visible := IsMetered and not FReadyToInstall; - shpMeteredWarning.Visible := IsMetered and not FReadyToInstall; - if IsMetered then + lblMeteredWarning.Visible := FIsMetered and not FReadyToInstall; + shpMeteredWarning.Visible := FIsMetered and not FReadyToInstall; + if FIsMetered then lblMeteredWarning.Caption := MsgFromId(S_Metered_Warning); end; diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index b6209fcf2c..5c9e05e30a 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -722,7 +722,7 @@ begin bucStateContext.SetRegistryState(usUpdateAvailable); if bucStateContext.FAutomaticUpdate and - not UtilNetworkConnection.IsBackgroundUpdateAllowed then + not UtilNetworkConnection.IsBackgroundUpdateBlocked then begin StartDownloadProcess; end; @@ -755,7 +755,7 @@ end; function UpdateAvailableState.HandleKmShell; begin if bucStateContext.FAutomaticUpdate and - not UtilNetworkConnection.IsBackgroundUpdateAllowed then + not UtilNetworkConnection.IsBackgroundUpdateBlocked then begin // we will use a new kmshell process to enable // the download as background process. diff --git a/windows/src/desktop/kmshell/main/UfrmMain.pas b/windows/src/desktop/kmshell/main/UfrmMain.pas index cbf4032bc5..485a199372 100644 --- a/windows/src/desktop/kmshell/main/UfrmMain.pas +++ b/windows/src/desktop/kmshell/main/UfrmMain.pas @@ -821,14 +821,14 @@ var IsMetered: Boolean; begin InstallNow := True; - // Confirm User is ok that this will require a reset - // The metered warning has been added to the update pop up. In the future - // it would be better to have this warning in a banner on the configuration - // page. IsMetered := UtilNetworkConnection.IsMetered; + // If a restarted is required (HasKeymanRun == True) + // OR it is a Metered connection warn the user and allow + // them to cancel their request to Install Now. + // Otherwise start installing. if HasKeymanRun OR IsMetered then begin - frmStartInstallNow := TfrmStartInstall.Create(nil, true); + frmStartInstallNow := TfrmStartInstall.Create(nil, HasKeymanRun, IsMetered); try if frmStartInstallNow.ShowModal = mrOk then InstallNow := True diff --git a/windows/src/desktop/kmshell/main/initprog.pas b/windows/src/desktop/kmshell/main/initprog.pas index 81c2fc7c82..e5b02c95f4 100644 --- a/windows/src/desktop/kmshell/main/initprog.pas +++ b/windows/src/desktop/kmshell/main/initprog.pas @@ -145,6 +145,7 @@ uses UILanguages, uninstall, UpgradeMnemonicLayout, + UtilNetworkConnection, utilfocusappwnd, utilkmshell, Keyman.System.UpdateStateMachine, @@ -669,6 +670,7 @@ function ShouldSendToBUpdateSM(FSilent: Boolean; BUpdateSM: TUpdateStateMachine; // UI elements from the state machine we have bring some of logic here. var frmStartInstall: TfrmStartInstall; + IsMetered: Boolean; ValidateReadyToInstall: Boolean; begin ValidateReadyToInstall := BUpdateSM.ValidateReadyToInstall; @@ -677,7 +679,8 @@ begin (FMode in [fmStart, fmSplash, fmMain, fmAbout, fmHelp, fmShowHelp, fmSettings, fmBoot]) then begin - frmStartInstall := TfrmStartInstall.Create(nil, false, ValidateReadyToInstall); + IsMetered := UtilNetworkConnection.IsMetered; + frmStartInstall := TfrmStartInstall.Create(nil, false, IsMetered, ValidateReadyToInstall); try Result := frmStartInstall.ShowModal = mrOk; finally diff --git a/windows/src/desktop/kmshell/util/UtilNetworkConnection.pas b/windows/src/desktop/kmshell/util/UtilNetworkConnection.pas index e5ade9826e..ad3615f1cc 100644 --- a/windows/src/desktop/kmshell/util/UtilNetworkConnection.pas +++ b/windows/src/desktop/kmshell/util/UtilNetworkConnection.pas @@ -26,16 +26,16 @@ function IsMetered: Boolean; function IsBackgroundDataRestricted: Boolean; (** - * Determines whether background updates are allowed. + * Determines whether background updates are blocked. * - * @returns True if updates can proceed, False if blocked by network constraints. + * @returns True if background updates are blocked, False if allowed. * * Note: Currently this checks for metered connection OR background data usage restricted. If a configuration item is added that provides the option to download on metered connections then this should be updated to include that logic *) -function IsBackgroundUpdateAllowed: Boolean; +function IsBackgroundUpdateBlocked: Boolean; implementation @@ -86,7 +86,7 @@ end; // data usage restricted. If a configuration item is added that // provides the option to download on metered connections then // this should be updated to include that logic -function IsBackgroundUpdateAllowed: Boolean; +function IsBackgroundUpdateBlocked: Boolean; begin Result := IsMetered OR IsBackgroundDataRestricted; end; From 981331802220967d11917ab254cd777476997ac3 Mon Sep 17 00:00:00 2001 From: Mengchou97 Date: Wed, 24 Jun 2026 13:22:03 +0700 Subject: [PATCH 067/140] removed whitespaces problems --- .../kMAPro/src/main/res/drawable/ic_action_timelapse.xml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_timelapse.xml b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_timelapse.xml index c3f9cfdcc8..249db341ff 100644 --- a/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_timelapse.xml +++ b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_timelapse.xml @@ -2,7 +2,4 @@ -
- - - + \ No newline at end of file From 8aa1d261f025baaf1e102d95ba99e4b443563321 Mon Sep 17 00:00:00 2001 From: Mengchou97 Date: Wed, 24 Jun 2026 13:32:23 +0700 Subject: [PATCH 068/140] added one whitespace back to be exactly the same as master --- .../KMAPro/kMAPro/src/main/res/drawable/ic_action_timelapse.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_timelapse.xml b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_timelapse.xml index 249db341ff..76d16034fa 100644 --- a/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_timelapse.xml +++ b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_timelapse.xml @@ -2,4 +2,4 @@ - \ No newline at end of file + From 82d558004ada164fc5689a1a91e47ecde80ddbe4 Mon Sep 17 00:00:00 2001 From: Mengchou97 Date: Wed, 24 Jun 2026 13:45:09 +0700 Subject: [PATCH 069/140] fixed whitespace problems --- android/KMAPro/kMAPro/src/main/AndroidManifest.xml | 1 - .../src/main/res/layout/activity_adjust_keyboard_height.xml | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/AndroidManifest.xml b/android/KMAPro/kMAPro/src/main/AndroidManifest.xml index 5de97baa87..563690c031 100644 --- a/android/KMAPro/kMAPro/src/main/AndroidManifest.xml +++ b/android/KMAPro/kMAPro/src/main/AndroidManifest.xml @@ -225,7 +225,6 @@ - + android:textColor="@color/main_text_color" + app:layout_constraintTop_toBottomOf="@id/titlebar" /> Date: Wed, 24 Jun 2026 14:38:42 +0700 Subject: [PATCH 070/140] Apply suggestions from code review Co-authored-by: MengHeng <90595388+Meng-Heng@users.noreply.github.com> --- .../java/com/tavultesoft/kmapro/MainActivity.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index b36da60da5..da48660db5 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -598,14 +598,14 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene getMenuInflater().inflate(R.menu.main, menu); this.menu = menu; - KMManager.getUpdateTool().addPropertyChangeListener(new PropertyChangeListener() + KMManager.getUpdateTool().addPropertyChangeListener(new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { - if (!evt.getPropertyName().equals("updateCount")) + if(!evt.getPropertyName().equals("updateCount")) return; updateUpdateCountIndicator( - evt.getNewValue() == null ? 0 : (Integer) evt.getNewValue()); + evt.getNewValue()==null?0:(Integer) evt.getNewValue()); } } ); @@ -848,7 +848,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene /** * Parse the URI data to determine the filename and URL for the .kmp keyboard package. * If URL is valid, download the kmp. - * @param packageUri URI to download the package. + * @param packageUri URI to download the package. * @param installMode KMP installation mode (silent, welcome only, or full) * TODO: only ever pass packageId and bcp47 from callers, as KMPLink should be responsible for * URL parsing, not this function. @@ -1123,7 +1123,6 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene /** * Based on whether Chrome and WebView are installed & enabled, * display the corresponding update panel - * * @param chromeStatus * @param webViewStatus */ @@ -1202,7 +1201,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene // TextView's default string is to update Chrome TextView textView = (TextView)findViewById(R.id.kmWebViewChromeTextView); textView.setText(String.format(getString(R.string.text_require_chrome_version), - WebViewUtils.KEYMAN_MIN_TARGET_VERSION_ANDROID_CHROME)); + WebViewUtils.KEYMAN_MIN_TARGET_VERSION_ANDROID_CHROME)); Button button = (Button)findViewById(R.id.webViewChromeButton); button.setText(getString(R.string.button_update_chrome)); From 89b8cdeca8e506a197ac37f97d80cf15336200d3 Mon Sep 17 00:00:00 2001 From: MengHeng <90595388+Meng-Heng@users.noreply.github.com> Date: Wed, 24 Jun 2026 14:40:37 +0700 Subject: [PATCH 071/140] Apply suggestions from code review Co-authored-by: MengHeng <90595388+Meng-Heng@users.noreply.github.com> --- .../KMAPro/kMAPro/src/main/res/drawable/ic_action_timelapse.xml | 2 -- 1 file changed, 2 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_timelapse.xml b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_timelapse.xml index 76d16034fa..d50e8fbaf5 100644 --- a/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_timelapse.xml +++ b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_timelapse.xml @@ -1,5 +1,3 @@ - - From 9d42a5f0a77a3778cfbab81963a7fb289772e316 Mon Sep 17 00:00:00 2001 From: Mengchou97 Date: Wed, 24 Jun 2026 14:48:39 +0700 Subject: [PATCH 072/140] remove whitespace on line 8 --- .../kMAPro/src/main/res/layout/fragment_select_language.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/android/KMAPro/kMAPro/src/main/res/layout/fragment_select_language.xml b/android/KMAPro/kMAPro/src/main/res/layout/fragment_select_language.xml index e06f78593b..df9b471d20 100644 --- a/android/KMAPro/kMAPro/src/main/res/layout/fragment_select_language.xml +++ b/android/KMAPro/kMAPro/src/main/res/layout/fragment_select_language.xml @@ -5,7 +5,6 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" - tools:context=".SelectLanguageFragment" > Date: Wed, 24 Jun 2026 15:48:54 +0700 Subject: [PATCH 073/140] restored master file --- .../kMAPro/src/main/res/drawable/ic_action_timelapse.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_timelapse.xml b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_timelapse.xml index d50e8fbaf5..c185c3bd11 100644 --- a/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_timelapse.xml +++ b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_timelapse.xml @@ -1,3 +1,5 @@ + - + + \ No newline at end of file From 3974f035f9ba1b3fcb327770a4714aa117a47c96 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Thu, 25 Jun 2026 10:49:35 +1000 Subject: [PATCH 074/140] feat(windows): use enum to drive installfrm layout --- ...yman.Configuration.UI.UfrmStartInstall.pas | 75 ++++++++++++------- windows/src/desktop/kmshell/main/UfrmMain.pas | 34 +++++++-- windows/src/desktop/kmshell/main/initprog.pas | 6 +- 3 files changed, 79 insertions(+), 36 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas index 28e456f691..d30cf44c6a 100644 --- a/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas +++ b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas @@ -1,7 +1,5 @@ { Keyman is copyright (C) SIL Global. MIT License. - - // TODO: #12887 Localise all the labels and captions. } unit Keyman.Configuration.UI.UfrmStartInstall; interface @@ -23,6 +21,15 @@ uses Vcl.Imaging.pngimage; type + // The 4 valid installation form scenarios + TInstallCase = ( + icNone, // Not a valid case, can be used as check before calling creating form + icRestartRequiredMetered, + icRestartRequiredNotMetered, + icReadyToInstallNotMetered, // Metered warning never needed if ReadyToInstall + icNoInstallMessageMetered + ); + TfrmStartInstall = class(TfrmKeymanBase) cmdInstall: TButton; cmdLater: TButton; @@ -32,18 +39,15 @@ type lblMeteredWarning: TLabel; procedure FormCreate(Sender: TObject); private - FRestartRequired: Boolean; - FIsMetered: Boolean; - FReadyToInstall: Boolean; + FScenario: TInstallCase; public - constructor Create( - AOwner: TComponent; - const RestartRequired: Boolean; - const IsMetered: Boolean; - const ReadyToInstall: Boolean = False); reintroduce; + constructor Create( + AOwner: TComponent; + const AScenario: TInstallCase); reintroduce; end; implementation + uses MessageIdentifiers, MessageIdentifierConsts; @@ -52,14 +56,11 @@ uses constructor TfrmStartInstall.Create( AOwner: TComponent; - const RestartRequired: Boolean; - const IsMetered: Boolean; - const ReadyToInstall: Boolean = False); + const AScenario: TInstallCase); begin + Assert(AScenario <> icNone, 'Invalid install case'); + FScenario := AScenario; inherited Create(AOwner); - FRestartRequired := RestartRequired; - FIsMetered := IsMetered; - FReadyToInstall := ReadyToInstall; end; procedure TfrmStartInstall.FormCreate(Sender: TObject); @@ -67,17 +68,39 @@ begin inherited; cmdInstall.Caption := MsgFromId(S_Update_Now); cmdLater.Caption := MsgFromId(S_Later); - if FRestartRequired then - lblUpdateMessage.Caption := MsgFromId(S_Update_Restart_Req) - else - lblUpdateMessage.Caption := MsgFromId(S_Ready_To_Install); - // Show warning if on a metered connection. If FReadyToInstall the update is - // already downloaded, so no use in displaying the warning. - lblMeteredWarning.Visible := FIsMetered and not FReadyToInstall; - shpMeteredWarning.Visible := FIsMetered and not FReadyToInstall; - if FIsMetered then - lblMeteredWarning.Caption := MsgFromId(S_Metered_Warning); + // Default UI configuration state - metered warnings hidden initially + lblUpdateMessage.Visible := True; + lblMeteredWarning.Visible := False; + shpMeteredWarning.Visible := False; + + case FScenario of + icRestartRequiredMetered: + begin + lblUpdateMessage.Caption := MsgFromId(S_Update_Restart_Req); + lblMeteredWarning.Caption := MsgFromId(S_Metered_Warning); + lblMeteredWarning.Visible := True; + shpMeteredWarning.Visible := True; + end; + + icRestartRequiredNotMetered: + begin + lblUpdateMessage.Caption := MsgFromId(S_Update_Restart_Req); + end; + + icReadyToInstallNotMetered: + begin + lblUpdateMessage.Caption := MsgFromId(S_Ready_To_Install); + end; + + icNoInstallMessageMetered: + begin + lblUpdateMessage.Visible := False; + lblMeteredWarning.Caption := MsgFromId(S_Metered_Warning); + lblMeteredWarning.Visible := True; + shpMeteredWarning.Visible := True; + end; + end; end; end. diff --git a/windows/src/desktop/kmshell/main/UfrmMain.pas b/windows/src/desktop/kmshell/main/UfrmMain.pas index 485a199372..b34007e0d1 100644 --- a/windows/src/desktop/kmshell/main/UfrmMain.pas +++ b/windows/src/desktop/kmshell/main/UfrmMain.pas @@ -819,16 +819,33 @@ var FResult, InstallNow: Boolean; frmStartInstallNow: TfrmStartInstall; IsMetered: Boolean; + EInstallScenario: TInstallCase; begin InstallNow := True; IsMetered := UtilNetworkConnection.IsMetered; + // If a restarted is required (HasKeymanRun == True) // OR it is a Metered connection warn the user and allow // them to cancel their request to Install Now. - // Otherwise start installing. - if HasKeymanRun OR IsMetered then + // Otherwise start installing with out pop-up warnings. + EInstallScenario := TInstallCase.icNone; + if HasKeymanRun and not IsMetered then begin - frmStartInstallNow := TfrmStartInstall.Create(nil, HasKeymanRun, IsMetered); + EInstallScenario := TInstallCase.icRestartRequiredNotMetered; + end + else if HasKeymanRun and IsMetered then + begin + EInstallScenario := TInstallCase.icRestartRequiredMetered; + end + else if (not HasKeymanRun) and IsMetered then + begin + EInstallScenario := TInstallCase.icNoInstallMessageMetered; + end; + + // Render dialog if conditions require it + if EInstallScenario <> TInstallCase.icNone then + begin + frmStartInstallNow := TfrmStartInstall.Create(nil, EInstallScenario); try if frmStartInstallNow.ShowModal = mrOk then InstallNow := True @@ -839,18 +856,23 @@ begin end; end; - if InstallNow = True then + // Process installation execution execution path + if InstallNow then begin ShellPath := TKeymanPaths.KeymanDesktopInstallPath(TKeymanPaths.S_KMShell); FResult := TUtilExecute.Shell(0, ShellPath, '', '-an'); if not FResult then + begin TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, - 'TrmfMain: Shell Execute Update_ApplyNow Failed') + 'TrmfMain: Shell Execute Update_ApplyNow Failed'); + end else - ModalResult := mrAbort; + begin // If a splash screen is currently open when "Install Now" is executed, // setting mrAbort ensures the splash screen is closed on the // return of "Keyman Configuration". + ModalResult := mrAbort; + end; end; end; diff --git a/windows/src/desktop/kmshell/main/initprog.pas b/windows/src/desktop/kmshell/main/initprog.pas index e5b02c95f4..997b791daf 100644 --- a/windows/src/desktop/kmshell/main/initprog.pas +++ b/windows/src/desktop/kmshell/main/initprog.pas @@ -145,7 +145,6 @@ uses UILanguages, uninstall, UpgradeMnemonicLayout, - UtilNetworkConnection, utilfocusappwnd, utilkmshell, Keyman.System.UpdateStateMachine, @@ -670,7 +669,6 @@ function ShouldSendToBUpdateSM(FSilent: Boolean; BUpdateSM: TUpdateStateMachine; // UI elements from the state machine we have bring some of logic here. var frmStartInstall: TfrmStartInstall; - IsMetered: Boolean; ValidateReadyToInstall: Boolean; begin ValidateReadyToInstall := BUpdateSM.ValidateReadyToInstall; @@ -679,8 +677,8 @@ begin (FMode in [fmStart, fmSplash, fmMain, fmAbout, fmHelp, fmShowHelp, fmSettings, fmBoot]) then begin - IsMetered := UtilNetworkConnection.IsMetered; - frmStartInstall := TfrmStartInstall.Create(nil, false, IsMetered, ValidateReadyToInstall); + // We are ready to install Metered warning not needed even if on Metered connection + frmStartInstall := TfrmStartInstall.Create(nil, TInstallCase.icReadyToInstallNotMetered); try Result := frmStartInstall.ShowModal = mrOk; finally From 85da9dc03c7953e8a1e176dce0d32b2aabff1ef0 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Thu, 25 Jun 2026 10:57:40 +1000 Subject: [PATCH 075/140] feat(windows): apply suggestions Co-authored-by: Eberhard Beilharz --- windows/src/desktop/kmshell/main/UfrmMain.pas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windows/src/desktop/kmshell/main/UfrmMain.pas b/windows/src/desktop/kmshell/main/UfrmMain.pas index b34007e0d1..c68a7d189a 100644 --- a/windows/src/desktop/kmshell/main/UfrmMain.pas +++ b/windows/src/desktop/kmshell/main/UfrmMain.pas @@ -824,7 +824,7 @@ begin InstallNow := True; IsMetered := UtilNetworkConnection.IsMetered; - // If a restarted is required (HasKeymanRun == True) + // If a restart is required (HasKeymanRun == True) // OR it is a Metered connection warn the user and allow // them to cancel their request to Install Now. // Otherwise start installing with out pop-up warnings. From 448d63e15672be6cbb349ae2fe2400008cf698be Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Thu, 25 Jun 2026 11:31:15 +1000 Subject: [PATCH 076/140] feat(windows): apply exception check Keyman currenlty only supports Windows 10 and 11 so the exception was low risk. If it is not available we return false as metered connections werent possilble before this the functionality will be the consistent with those windows versions. i.e. always updating in the background --- ...yman.Configuration.UI.UfrmStartInstall.pas | 2 +- .../kmshell/util/UtilNetworkConnection.pas | 42 ++++++++++++------- 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas index d30cf44c6a..78578e66c0 100644 --- a/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas +++ b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas @@ -21,7 +21,7 @@ uses Vcl.Imaging.pngimage; type - // The 4 valid installation form scenarios + // The 4 valid installation form scenarios plus a None case for validation TInstallCase = ( icNone, // Not a valid case, can be used as check before calling creating form icRestartRequiredMetered, diff --git a/windows/src/desktop/kmshell/util/UtilNetworkConnection.pas b/windows/src/desktop/kmshell/util/UtilNetworkConnection.pas index ad3615f1cc..02952cafb2 100644 --- a/windows/src/desktop/kmshell/util/UtilNetworkConnection.pas +++ b/windows/src/desktop/kmshell/util/UtilNetworkConnection.pas @@ -51,18 +51,25 @@ var CostLevel: IConnectionCost; begin Result := False; - // Get the profile currently providing internet access - Profile := TNetworkInformation.GetInternetConnectionProfile; - - if Profile <> nil then - begin - CostLevel := Profile.GetConnectionCost; - Result := (CostLevel.NetworkCostType <> NetworkCostType.Unrestricted) - or CostLevel.Roaming - or CostLevel.OverDataLimit; + try + // Get the profile currently providing internet access + Profile := TNetworkInformation.GetInternetConnectionProfile; + if Profile <> nil then + begin + CostLevel := Profile.GetConnectionCost; + if CostLevel <> nil then + Result := (CostLevel.NetworkCostType <> NetworkCostType.Unrestricted) + or CostLevel.Roaming + or CostLevel.OverDataLimit; + end; + except + on E: Exception do + // If the WinRT network APIs are unavailable or throw (e.g. unusual + // Windows SKUs, containers, Network List Manager service stopped), + // treat the connection as non-metered so updates are not blocked. + Result := False; end; end; - function IsBackgroundDataRestricted: Boolean; var Profile: IConnectionProfile; @@ -70,15 +77,18 @@ var DataRestriction: IConnectionCost2; begin Result := False; - Profile := TNetworkInformation.GetInternetConnectionProfile; - if Profile <> nil then - begin - CostLevel := Profile.GetConnectionCost; - if (CostLevel <> nil) and Supports(CostLevel, IConnectionCost2, DataRestriction) then + try + Profile := TNetworkInformation.GetInternetConnectionProfile; + if Profile <> nil then begin + CostLevel := Profile.GetConnectionCost; + if (CostLevel <> nil) and Supports(CostLevel, IConnectionCost2, DataRestriction) then Result := DataRestriction.BackgroundDataUsageRestricted; - Exit; end; + except + on E: Exception do + // See IsMetered: default to not-restricted if the WinRT APIs throw. + Result := False; end; end; From 0bd7bf1ed2b672ce10054c82a1c17419dce24aeb Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Tue, 30 Jun 2026 13:15:05 +1000 Subject: [PATCH 077/140] feat(windows): apply suggestions from code review Co-authored-by: Marc Durdin --- windows/src/desktop/kmshell/main/UfrmMain.pas | 2 +- windows/src/desktop/kmshell/main/initprog.pas | 4 ++-- windows/src/desktop/kmshell/util/UtilNetworkConnection.pas | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/windows/src/desktop/kmshell/main/UfrmMain.pas b/windows/src/desktop/kmshell/main/UfrmMain.pas index c68a7d189a..6fe675ec30 100644 --- a/windows/src/desktop/kmshell/main/UfrmMain.pas +++ b/windows/src/desktop/kmshell/main/UfrmMain.pas @@ -819,7 +819,7 @@ var FResult, InstallNow: Boolean; frmStartInstallNow: TfrmStartInstall; IsMetered: Boolean; - EInstallScenario: TInstallCase; + InstallCase: TInstallCase; begin InstallNow := True; IsMetered := UtilNetworkConnection.IsMetered; diff --git a/windows/src/desktop/kmshell/main/initprog.pas b/windows/src/desktop/kmshell/main/initprog.pas index 997b791daf..bb34fedef9 100644 --- a/windows/src/desktop/kmshell/main/initprog.pas +++ b/windows/src/desktop/kmshell/main/initprog.pas @@ -677,8 +677,8 @@ begin (FMode in [fmStart, fmSplash, fmMain, fmAbout, fmHelp, fmShowHelp, fmSettings, fmBoot]) then begin - // We are ready to install Metered warning not needed even if on Metered connection - frmStartInstall := TfrmStartInstall.Create(nil, TInstallCase.icReadyToInstallNotMetered); + // We are ready to install Metered warning not needed even if on Metered connection + frmStartInstall := TfrmStartInstall.Create(nil, TInstallCase.icReadyToInstallNotMetered); try Result := frmStartInstall.ShowModal = mrOk; finally diff --git a/windows/src/desktop/kmshell/util/UtilNetworkConnection.pas b/windows/src/desktop/kmshell/util/UtilNetworkConnection.pas index 02952cafb2..d1e89e2be4 100644 --- a/windows/src/desktop/kmshell/util/UtilNetworkConnection.pas +++ b/windows/src/desktop/kmshell/util/UtilNetworkConnection.pas @@ -54,7 +54,7 @@ begin try // Get the profile currently providing internet access Profile := TNetworkInformation.GetInternetConnectionProfile; - if Profile <> nil then + if Assigned(Profile) then begin CostLevel := Profile.GetConnectionCost; if CostLevel <> nil then From 60094738d9f5320d5a5258a3260858182985316b Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Tue, 30 Jun 2026 14:42:49 +1000 Subject: [PATCH 078/140] feat(windows): wrap network util in class --- windows/src/desktop/kmshell/kmshell.dpr | 2 +- windows/src/desktop/kmshell/kmshell.dproj | 14 ++-- .../main/Keyman.System.UpdateStateMachine.pas | 8 +-- windows/src/desktop/kmshell/main/UfrmMain.pas | 16 ++--- ....Configuration.Util.NetworkConnection.pas} | 68 ++++++++++--------- 5 files changed, 55 insertions(+), 53 deletions(-) rename windows/src/desktop/kmshell/util/{UtilNetworkConnection.pas => Keyman.Configuration.Util.NetworkConnection.pas} (56%) diff --git a/windows/src/desktop/kmshell/kmshell.dpr b/windows/src/desktop/kmshell/kmshell.dpr index 214c8c86a3..e2d28b88d2 100644 --- a/windows/src/desktop/kmshell/kmshell.dpr +++ b/windows/src/desktop/kmshell/kmshell.dpr @@ -183,7 +183,7 @@ uses Keyman.System.DownloadUpdate in 'main\Keyman.System.DownloadUpdate.pas', Keyman.System.ExecutionHistory in '..\..\..\..\common\windows\delphi\general\Keyman.System.ExecutionHistory.pas', Keyman.Configuration.UI.UfrmStartInstall in 'main\Keyman.Configuration.UI.UfrmStartInstall.pas' {frmStartInstall}, - UtilNetworkConnection in 'util\UtilNetworkConnection.pas'; + Keyman.Configuration.Util.NetworkConnection in 'util\Keyman.Configuration.Util.NetworkConnection.pas'; {$R VERSION.RES} {$R manifest.res} diff --git a/windows/src/desktop/kmshell/kmshell.dproj b/windows/src/desktop/kmshell/kmshell.dproj index fbe3df5702..371ce4cd49 100644 --- a/windows/src/desktop/kmshell/kmshell.dproj +++ b/windows/src/desktop/kmshell/kmshell.dproj @@ -357,7 +357,7 @@
frmStartInstall
- + Cfg_2 @@ -419,12 +419,6 @@ False - - - kmshell.exe - true - - .\ @@ -437,6 +431,12 @@ true + + + kmshell.exe + true + + 1 diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index 5c9e05e30a..700fe322a4 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -14,10 +14,10 @@ uses Sentry.Client, KeymanPaths, + Keyman.Configuration.Util.NetworkConnection, Keyman.System.ExecutionHistory, Keyman.System.UpdateCheckResponse, - utilkmshell, - UtilNetworkConnection; + utilkmshell; type EUpdateStateMachine = class(Exception); @@ -722,7 +722,7 @@ begin bucStateContext.SetRegistryState(usUpdateAvailable); if bucStateContext.FAutomaticUpdate and - not UtilNetworkConnection.IsBackgroundUpdateBlocked then + not TNetworkConnection.IsBackgroundUpdateBlocked then begin StartDownloadProcess; end; @@ -755,7 +755,7 @@ end; function UpdateAvailableState.HandleKmShell; begin if bucStateContext.FAutomaticUpdate and - not UtilNetworkConnection.IsBackgroundUpdateBlocked then + not TNetworkConnection.IsBackgroundUpdateBlocked then begin // we will use a new kmshell process to enable // the download as background process. diff --git a/windows/src/desktop/kmshell/main/UfrmMain.pas b/windows/src/desktop/kmshell/main/UfrmMain.pas index 6fe675ec30..4be5c20525 100644 --- a/windows/src/desktop/kmshell/main/UfrmMain.pas +++ b/windows/src/desktop/kmshell/main/UfrmMain.pas @@ -190,6 +190,7 @@ uses Keyman.System.KeymanSentryClient, Keyman.System.UpdateStateMachine, OptionsXMLRenderer, + Keyman.Configuration.Util.NetworkConnection, Keyman.Configuration.System.UmodWebHttpServer, Keyman.Configuration.System.HttpServer.App.ConfigMain, Keyman.Configuration.UI.InstallFile, @@ -212,7 +213,6 @@ uses utilexecute, utilkmshell, utilhttp, - UtilNetworkConnection, utiluac, utilxml, KeymanPaths; @@ -822,30 +822,30 @@ var InstallCase: TInstallCase; begin InstallNow := True; - IsMetered := UtilNetworkConnection.IsMetered; + IsMetered := TNetworkConnection.IsMetered; // If a restart is required (HasKeymanRun == True) // OR it is a Metered connection warn the user and allow // them to cancel their request to Install Now. // Otherwise start installing with out pop-up warnings. - EInstallScenario := TInstallCase.icNone; + InstallCase := TInstallCase.icNone; if HasKeymanRun and not IsMetered then begin - EInstallScenario := TInstallCase.icRestartRequiredNotMetered; + InstallCase := TInstallCase.icRestartRequiredNotMetered; end else if HasKeymanRun and IsMetered then begin - EInstallScenario := TInstallCase.icRestartRequiredMetered; + InstallCase := TInstallCase.icRestartRequiredMetered; end else if (not HasKeymanRun) and IsMetered then begin - EInstallScenario := TInstallCase.icNoInstallMessageMetered; + InstallCase := TInstallCase.icNoInstallMessageMetered; end; // Render dialog if conditions require it - if EInstallScenario <> TInstallCase.icNone then + if InstallCase <> TInstallCase.icNone then begin - frmStartInstallNow := TfrmStartInstall.Create(nil, EInstallScenario); + frmStartInstallNow := TfrmStartInstall.Create(nil, InstallCase); try if frmStartInstallNow.ShowModal = mrOk then InstallNow := True diff --git a/windows/src/desktop/kmshell/util/UtilNetworkConnection.pas b/windows/src/desktop/kmshell/util/Keyman.Configuration.Util.NetworkConnection.pas similarity index 56% rename from windows/src/desktop/kmshell/util/UtilNetworkConnection.pas rename to windows/src/desktop/kmshell/util/Keyman.Configuration.Util.NetworkConnection.pas index d1e89e2be4..ef9824fa48 100644 --- a/windows/src/desktop/kmshell/util/UtilNetworkConnection.pas +++ b/windows/src/desktop/kmshell/util/Keyman.Configuration.Util.NetworkConnection.pas @@ -3,40 +3,41 @@ * * Notes: Enable checking for metered connection and background data restrictions. *) -unit UtilNetworkConnection; +unit Keyman.Configuration.Util.NetworkConnection; interface -(** - * Checks if the current internet connection is restricted, roaming, or over its - * data limit. - * This learn microsoft article shows how to combine network costs to determine - * if the connection is metered. - * https://learn.microsoft.com/en-us/uwp/api/windows.networking.connectivity.connectionprofile?view=winrt-28000 - * - * @returns True if the connection is metered, False otherwise. - *) -function IsMetered: Boolean; - -(** - * Checks if background data usage is explicitly restricted by the current network profile. - * - * @returns True if background data usage is restricted, False otherwise. - *) -function IsBackgroundDataRestricted: Boolean; - -(** - * Determines whether background updates are blocked. - * - * @returns True if background updates are blocked, False if allowed. - * - * Note: Currently this checks for metered connection OR background - data usage restricted. If a configuration item is added that - provides the option to download on metered connections then - this should be updated to include that logic - *) -function IsBackgroundUpdateBlocked: Boolean; +type + TNetworkConnection = class + (** + * Checks if the current internet connection is restricted, roaming, or over its + * data limit. + * This learn microsoft article shows how to combine network costs to determine + * if the connection is metered. + * https://learn.microsoft.com/en-us/uwp/api/windows.networking.connectivity.connectionprofile?view=winrt-28000 + * + * @returns True if the connection is metered, False otherwise. + *) + class function IsMetered: Boolean; static; + (** + * Checks if background data usage is explicitly restricted by the current network profile. + * + * @returns True if background data usage is restricted, False otherwise. + *) + class function IsBackgroundDataRestricted: Boolean; static; + (** + * Determines whether background updates are blocked. + * + * @returns True if background updates are blocked, False if allowed. + * + * Note: Currently this checks for metered connection OR background + data usage restricted. If a configuration item is added that + provides the option to download on metered connections then + this should be updated to include that logic + *) + class function IsBackgroundUpdateBlocked: Boolean; static; + end; implementation uses @@ -45,7 +46,7 @@ uses Winapi.WinRT, Winapi.Networking.Connectivity; -function IsMetered: Boolean; +class function TNetworkConnection.IsMetered: Boolean; var Profile: IConnectionProfile; CostLevel: IConnectionCost; @@ -70,7 +71,8 @@ begin Result := False; end; end; -function IsBackgroundDataRestricted: Boolean; + +class function TNetworkConnection.IsBackgroundDataRestricted: Boolean; var Profile: IConnectionProfile; CostLevel: IConnectionCost; @@ -96,7 +98,7 @@ end; // data usage restricted. If a configuration item is added that // provides the option to download on metered connections then // this should be updated to include that logic -function IsBackgroundUpdateBlocked: Boolean; +class function TNetworkConnection.IsBackgroundUpdateBlocked: Boolean; begin Result := IsMetered OR IsBackgroundDataRestricted; end; From 0e5c94c7e56968ad850fb1d43c6a3add26dc39c6 Mon Sep 17 00:00:00 2001 From: Sabine Date: Thu, 2 Jul 2026 09:07:49 +0200 Subject: [PATCH 079/140] chore(common): add JPathOrMatcher for use of fast-xml-parser 5.8 --- developer/src/common/web/utils/src/xml-utils.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/developer/src/common/web/utils/src/xml-utils.ts b/developer/src/common/web/utils/src/xml-utils.ts index 9da6c00071..8f1fa6a88c 100644 --- a/developer/src/common/web/utils/src/xml-utils.ts +++ b/developer/src/common/web/utils/src/xml-utils.ts @@ -6,7 +6,7 @@ * Abstraction for XML reading and writing */ -import { XMLParser, XMLBuilder, XMLMetaData, X2jOptions, XmlBuilderOptions } from 'fast-xml-parser'; +import { XMLParser, XMLBuilder, XMLMetaData, X2jOptions, XmlBuilderOptions, JPathOrMatcher } from 'fast-xml-parser'; import { SymbolUtils } from "./symbol-utils.js"; /** Symbol giving the start offset, in chars, of the node */ @@ -33,9 +33,9 @@ const PARSER_COMMON_OPTIONS: X2jOptions = { ignoreAttributes: false, ignorePiTags: true, numberParseOptions: { // TODO: query is this option really necessary? - eNotation: null, - hex: null, - leadingZeros: null, + eNotation: false, + hex: false, + leadingZeros: false, skipLike: /(?:)/, // parse numbers as strings }, textNodeName: '_', @@ -47,7 +47,7 @@ const PARSER_OPTIONS: KeymanXMLParserOptionsBag = { attributeNamePrefix: '@__', // We'll use this to convert attributes to strings and subobjects to arrays, when empty. htmlEntities: true, ignoreAttributes: false, // We'd like attributes, please - tagValueProcessor: (_tagName: string, tagValue: string /*, jPath, hasAttributes, isLeafNode*/) => { + tagValueProcessor: (_tagName: string, tagValue: string /*, _jPathOrMatcher: JPathOrMatcher, _hasAttributes: boolean, _isLeafNode: boolean*/) => { // since trimValues: false, we need to zap any element values that would be trimmed. // currently, the LDML spec doesn't have any element values, but this // future-proofs us a little in that element values are allowed, just trimmed. @@ -73,11 +73,11 @@ const PARSER_OPTIONS: KeymanXMLParserOptionsBag = { }, 'kvks': { ...PARSER_COMMON_OPTIONS, - tagValueProcessor: (_tagName: string, tagValue: string, _jPath: string, _hasAttributes: boolean, isLeafNode: boolean) : string | undefined => { + tagValueProcessor: (_tagName: string, tagValue: string, _jPathOrMatcher: JPathOrMatcher, _hasAttributes: boolean, isLeafNode: boolean) : unknown => { if (!isLeafNode) { return tagValue?.trim(); // trimmed value } else { - return null; // no change to leaf nodes + return undefined; // no change to leaf nodes } }, trimValues: false, // preserve spaces @@ -396,4 +396,4 @@ export function withOffset(c: number, compileContext?: any) : KeymanXMLMetadata startIndex: c }); return o; -} +} \ No newline at end of file From 5ab4b10375cbda435a5357b7a241f8807d7c930c Mon Sep 17 00:00:00 2001 From: Sabine Date: Thu, 2 Jul 2026 09:46:48 +0200 Subject: [PATCH 080/140] chore(common): dummy change --- common/include/keyman_vkey.h | 1 + 1 file changed, 1 insertion(+) diff --git a/common/include/keyman_vkey.h b/common/include/keyman_vkey.h index 59ecbfa9cf..7dd525a426 100644 --- a/common/include/keyman_vkey.h +++ b/common/include/keyman_vkey.h @@ -5,6 +5,7 @@ * VKCodes for keyman */ + namespace km_vk { #define VK_LBUTTON 0x01 From 490eb93eaf994b7ec3b0db9dc9a3f2aff42b1f58 Mon Sep 17 00:00:00 2001 From: Sabine Date: Thu, 2 Jul 2026 10:00:34 +0200 Subject: [PATCH 081/140] chore(common): change path for #include /keymanversion.h --- developer/src/kmcmplib/src/Compiler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/developer/src/kmcmplib/src/Compiler.cpp b/developer/src/kmcmplib/src/Compiler.cpp index 6dc8646ddd..f51d6b122e 100644 --- a/developer/src/kmcmplib/src/Compiler.cpp +++ b/developer/src/kmcmplib/src/Compiler.cpp @@ -81,7 +81,7 @@ //_S2 ======== //_S2 which version? here from chore/developer/scatteredCode/cleanup_VKeys // TODO: These three should be under common/cpp/include -- not windows specific -#include "../../../../common/windows/cpp/include/keymanversion.h" +//#include "../../../../common/windows/cpp/include/keymanversion.h" #include "../../../../common/windows/cpp/include/crc32.h" #include "../../../../common/windows/cpp/include/ConvertUTF.h" #include "../../../../common/include/keyman_vkey.h" From a19e452b592a99ff5558ac85dd470552f3efb864 Mon Sep 17 00:00:00 2001 From: Sabine Date: Thu, 2 Jul 2026 10:22:41 +0200 Subject: [PATCH 082/140] chore(common): change use of #includes --- developer/src/kmcmplib/src/Compiler.cpp | 4 ++-- developer/src/kmdecomp/savekeyboard.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/developer/src/kmcmplib/src/Compiler.cpp b/developer/src/kmcmplib/src/Compiler.cpp index f51d6b122e..dd9ca500cb 100644 --- a/developer/src/kmcmplib/src/Compiler.cpp +++ b/developer/src/kmcmplib/src/Compiler.cpp @@ -82,8 +82,8 @@ //_S2 which version? here from chore/developer/scatteredCode/cleanup_VKeys // TODO: These three should be under common/cpp/include -- not windows specific //#include "../../../../common/windows/cpp/include/keymanversion.h" -#include "../../../../common/windows/cpp/include/crc32.h" -#include "../../../../common/windows/cpp/include/ConvertUTF.h" +//#include "../../../../common/windows/cpp/include/crc32.h" +//#include "../../../../common/windows/cpp/include/ConvertUTF.h" #include "../../../../common/include/keyman_vkey.h" //_S2 ======== diff --git a/developer/src/kmdecomp/savekeyboard.cpp b/developer/src/kmdecomp/savekeyboard.cpp index ef4a927b87..244f6fd416 100644 --- a/developer/src/kmdecomp/savekeyboard.cpp +++ b/developer/src/kmdecomp/savekeyboard.cpp @@ -36,7 +36,7 @@ // _S2 which version? here from chore/developer/scatteredCode/cleanup_VKeys -#include "../../../common/windows/cpp/include/vkeys.h" +//#include "../../../common/windows/cpp/include/vkeys.h" #include "../../../common/include/keyman_vkey.h" using namespace km_vk; From 62d7f79925e6f7b6fb17fd91b77a831be2e06ef5 Mon Sep 17 00:00:00 2001 From: Sabine Date: Thu, 2 Jul 2026 11:19:46 +0200 Subject: [PATCH 083/140] chore(common): rename and use keyman_vkey.h-> km_vkey.h --- common/include/{keyman_vkey.h => km_vkey.h} | 3 +-- core/src/kmx/kmx_base.h | 2 +- core/src/kmx/kmx_processevent.h | 2 +- developer/src/kmanalyze/kmanalyze.cpp | 2 +- developer/src/kmcmplib/src/CharToKeyConversion.cpp | 2 +- developer/src/kmcmplib/src/Compiler.cpp | 2 +- developer/src/kmdecomp/savekeyboard.cpp | 2 +- developer/src/samples/imsample/imsample.cpp | 2 +- linux/mcompile/keymap/keymap.h | 2 +- mac/mcompile/keymap.h | 4 ++-- 10 files changed, 11 insertions(+), 12 deletions(-) rename common/include/{keyman_vkey.h => km_vkey.h} (99%) diff --git a/common/include/keyman_vkey.h b/common/include/km_vkey.h similarity index 99% rename from common/include/keyman_vkey.h rename to common/include/km_vkey.h index 7dd525a426..a8764e881d 100644 --- a/common/include/keyman_vkey.h +++ b/common/include/km_vkey.h @@ -1,11 +1,10 @@ /* * Keyman is copyright (C) SIL International. MIT License. - * Create Date: 16 Jul 2025 + * Create Date: 2 Jul 2026 * Author: Sabine Schmitt * VKCodes for keyman */ - namespace km_vk { #define VK_LBUTTON 0x01 diff --git a/core/src/kmx/kmx_base.h b/core/src/kmx/kmx_base.h index c1353540c4..c2c6fb5722 100644 --- a/core/src/kmx/kmx_base.h +++ b/core/src/kmx/kmx_base.h @@ -1,7 +1,7 @@ #pragma once #include "keyman_core.h" -#include "../../../common/include/keyman_vkey.h" +#include "../../../common/include/km_vkey.h" #if defined(_WIN32) || defined(_WIN64) #define snprintf _snprintf diff --git a/core/src/kmx/kmx_processevent.h b/core/src/kmx/kmx_processevent.h index 6296a216b5..53948e0f1c 100644 --- a/core/src/kmx/kmx_processevent.h +++ b/core/src/kmx/kmx_processevent.h @@ -23,7 +23,7 @@ #include "kmx_options.h" #include "kmx_environment.h" #include "kmx_debugger.h" -#include "../../../common/include/keyman_vkey.h" +#include "../../../common/include/km_vkey.h" /***************************************************************************/ diff --git a/developer/src/kmanalyze/kmanalyze.cpp b/developer/src/kmanalyze/kmanalyze.cpp index baeeafdb8c..4d94aeaacd 100644 --- a/developer/src/kmanalyze/kmanalyze.cpp +++ b/developer/src/kmanalyze/kmanalyze.cpp @@ -7,7 +7,7 @@ #include #include #include -#include "../../../common/include/keyman_vkey.h" +#include "../../../common/include/km_vkey.h" using namespace km_vk; diff --git a/developer/src/kmcmplib/src/CharToKeyConversion.cpp b/developer/src/kmcmplib/src/CharToKeyConversion.cpp index b25a813bb0..5aaed7d409 100644 --- a/developer/src/kmcmplib/src/CharToKeyConversion.cpp +++ b/developer/src/kmcmplib/src/CharToKeyConversion.cpp @@ -1,7 +1,7 @@ #include "pch.h" #include "CharToKeyConversion.h" -#include "../../../../common/include/keyman_vkey.h" +#include "../../../../common/include/km_vkey.h" /* Following VKeys come from Windows */ diff --git a/developer/src/kmcmplib/src/Compiler.cpp b/developer/src/kmcmplib/src/Compiler.cpp index dd9ca500cb..c04dcc57fa 100644 --- a/developer/src/kmcmplib/src/Compiler.cpp +++ b/developer/src/kmcmplib/src/Compiler.cpp @@ -84,7 +84,7 @@ //#include "../../../../common/windows/cpp/include/keymanversion.h" //#include "../../../../common/windows/cpp/include/crc32.h" //#include "../../../../common/windows/cpp/include/ConvertUTF.h" -#include "../../../../common/include/keyman_vkey.h" +#include "../../../../common/include/km_vkey.h" //_S2 ======== //_S2 which version? here from master diff --git a/developer/src/kmdecomp/savekeyboard.cpp b/developer/src/kmdecomp/savekeyboard.cpp index 244f6fd416..680d1442c3 100644 --- a/developer/src/kmdecomp/savekeyboard.cpp +++ b/developer/src/kmdecomp/savekeyboard.cpp @@ -37,7 +37,7 @@ // _S2 which version? here from chore/developer/scatteredCode/cleanup_VKeys //#include "../../../common/windows/cpp/include/vkeys.h" -#include "../../../common/include/keyman_vkey.h" +#include "../../../common/include/km_vkey.h" using namespace km_vk; //_S2 ======== diff --git a/developer/src/samples/imsample/imsample.cpp b/developer/src/samples/imsample/imsample.cpp index 3e299fda7c..c3cc2a6cb4 100644 --- a/developer/src/samples/imsample/imsample.cpp +++ b/developer/src/samples/imsample/imsample.cpp @@ -21,7 +21,7 @@ #include #include #include "imlib.h" -#include "../../../../common/include/keyman_vkey.h" +#include "../../../../common/include/km_vkey.h" using namespace km_vk; diff --git a/linux/mcompile/keymap/keymap.h b/linux/mcompile/keymap/keymap.h index 46b5b2a31f..6dddd05155 100644 --- a/linux/mcompile/keymap/keymap.h +++ b/linux/mcompile/keymap/keymap.h @@ -15,7 +15,7 @@ #include #include "km_u16.h" #include -#include "../../../common/include/keyman_vkey.h" +#include "../../../common/include/km_vkey.h" enum ShiftState { diff --git a/mac/mcompile/keymap.h b/mac/mcompile/keymap.h index 9eb98a0325..92c9c04099 100644 --- a/mac/mcompile/keymap.h +++ b/mac/mcompile/keymap.h @@ -9,8 +9,8 @@ #include "../../common/include/km_u16.h" -#include "../../common/include/keyman_vkey.h" -//#include "../../../../common/include/keyman_vkey.h" +#include "../../common/include/km_vkey.h" +//#include "../../../../common/include/km_vkey.h" enum ShiftState { Base = 0, // 0 Shft = 1, // 1 From 68fa9b97fb0b0c1ff67581e0bdde00c891a706d6 Mon Sep 17 00:00:00 2001 From: Sabine Date: Thu, 2 Jul 2026 11:37:46 +0200 Subject: [PATCH 084/140] chore(common): rename namespace km_vk to vkey and use under km (km::vkey) --- common/include/km_vkey.h | 278 +++++++++--------- core/src/kmx/kmx_actions.cpp | 2 +- core/src/kmx/kmx_capslock.cpp | 2 +- core/src/kmx/kmx_context.cpp | 2 +- core/src/kmx/kmx_conversion.cpp | 2 +- core/src/kmx/kmx_debugger.cpp | 2 +- core/src/kmx/kmx_environment.cpp | 2 +- core/src/kmx/kmx_file.cpp | 2 +- core/src/kmx/kmx_modifiers.cpp | 2 +- core/src/kmx/kmx_options.cpp | 2 +- core/src/kmx/kmx_processevent.cpp | 2 +- core/src/kmx/kmx_processor.cpp | 2 +- core/src/kmx/kmx_xstring.cpp | 2 +- core/tests/unit/kmnkbd/debug_api.tests.cpp | 2 +- core/tests/unit/kmnkbd/kmx_context.tests.cpp | 2 +- core/tests/unit/kmnkbd/kmx_xstring.tests.cpp | 2 +- .../unit/kmx/kmx_external_event.tests.cpp | 2 +- core/tests/unit/kmx/kmx_imx.tests.cpp | 2 +- core/tests/unit/kmx/kmx_key_list.tests.cpp | 2 +- core/tests/unit/ldml/kmx_plus.tests.cpp | 2 +- core/tests/unit/ldml/transforms.tests.cpp | 2 +- developer/src/kmanalyze/kmanalyze.cpp | 2 +- developer/src/kmcmplib/src/Compiler.cpp | 2 +- developer/src/kmdecomp/savekeyboard.cpp | 2 +- developer/src/samples/imsample/imsample.cpp | 2 +- 25 files changed, 164 insertions(+), 162 deletions(-) diff --git a/common/include/km_vkey.h b/common/include/km_vkey.h index a8764e881d..03282ac5d6 100644 --- a/common/include/km_vkey.h +++ b/common/include/km_vkey.h @@ -5,158 +5,160 @@ * VKCodes for keyman */ -namespace km_vk { +namespace km { +namespace vkey { -#define VK_LBUTTON 0x01 -#define VK_RBUTTON 0x02 -#define VK_CANCEL 0x03 -#define VK_MBUTTON 0x04 +#define VK_LBUTTON 0x01 +#define VK_RBUTTON 0x02 +#define VK_CANCEL 0x03 +#define VK_MBUTTON 0x04 -#define VK_BACKSPACE 0x08 -#define VK_BACK 0x08 -#define VK_TAB 0x09 +#define VK_BACKSPACE 0x08 +#define VK_BACK 0x08 +#define VK_TAB 0x09 -#define VK_CLEAR 0x0C -#define VK_RETURN 0x0D -#define VK_ENTER 0x0D +#define VK_CLEAR 0x0C +#define VK_RETURN 0x0D +#define VK_ENTER 0x0D -#define VK_SHIFT 0x10 -#define VK_CONTROL 0x11 -#define VK_MENU 0x12 -#define VK_PAUSE 0x13 -#define VK_CAPITAL 0x14 -#define VK_KANA 0x15 -#define VK_HANGEUL 0x15 -#define VK_HANGUL 0x15 -#define VK_IME_ON 0x16 -#define VK_JUNJA 0x17 -#define VK_FINAL 0x18 -#define VK_HANJA 0x19 -#define VK_KANJI 0x19 -#define VK_IME_OFF 0x1A -#define VK_ESCAPE 0x1B -#define VK_CONVERT 0x1C -#define VK_NONCONVERT 0x1D -#define VK_ACCEPT 0x1E -#define VK_MODECHANGE 0x1F -#define VK_SPACE 0x20 -#define VK_PRIOR 0x21 -#define VK_NEXT 0x22 -#define VK_END 0x23 -#define VK_HOME 0x24 -#define VK_LEFT 0x25 -#define VK_UP 0x26 -#define VK_RIGHT 0x27 -#define VK_DOWN 0x28 -#define VK_SELECT 0x29 -#define VK_PRINT 0x2A -#define VK_EXECUTE 0x2B -#define VK_SNAPSHOT 0x2C -#define VK_INSERT 0x2D -//#define VK_DECIMAL 0x2E // _S2 if I use it here kmdecomp complains because of duplicate macro definition here and in WinUser.h +#define VK_SHIFT 0x10 +#define VK_CONTROL 0x11 +#define VK_MENU 0x12 +#define VK_PAUSE 0x13 +#define VK_CAPITAL 0x14 +#define VK_KANA 0x15 +#define VK_HANGEUL 0x15 +#define VK_HANGUL 0x15 +#define VK_IME_ON 0x16 +#define VK_JUNJA 0x17 +#define VK_FINAL 0x18 +#define VK_HANJA 0x19 +#define VK_KANJI 0x19 +#define VK_IME_OFF 0x1A +#define VK_ESCAPE 0x1B +#define VK_CONVERT 0x1C +#define VK_NONCONVERT 0x1D +#define VK_ACCEPT 0x1E +#define VK_MODECHANGE 0x1F +#define VK_SPACE 0x20 +#define VK_PRIOR 0x21 +#define VK_NEXT 0x22 +#define VK_END 0x23 +#define VK_HOME 0x24 +#define VK_LEFT 0x25 +#define VK_UP 0x26 +#define VK_RIGHT 0x27 +#define VK_DOWN 0x28 +#define VK_SELECT 0x29 +#define VK_PRINT 0x2A +#define VK_EXECUTE 0x2B +#define VK_SNAPSHOT 0x2C +#define VK_INSERT 0x2D +// #define VK_DECIMAL 0x2E // _S2 if I use it here kmdecomp complains because of duplicate macro definition here and in +// WinUser.h -#define VK_KEY_0 0x30 -#define VK_KEY_1 0x31 -#define VK_KEY_2 0x32 -#define VK_KEY_3 0x33 -#define VK_KEY_4 0x34 -#define VK_KEY_5 0x35 -#define VK_KEY_6 0x36 -#define VK_KEY_7 0x37 -#define VK_KEY_8 0x38 -#define VK_KEY_9 0x39 +#define VK_KEY_0 0x30 +#define VK_KEY_1 0x31 +#define VK_KEY_2 0x32 +#define VK_KEY_3 0x33 +#define VK_KEY_4 0x34 +#define VK_KEY_5 0x35 +#define VK_KEY_6 0x36 +#define VK_KEY_7 0x37 +#define VK_KEY_8 0x38 +#define VK_KEY_9 0x39 -#define VK_KEY_A 0x41 -#define VK_KEY_B 0x42 -#define VK_KEY_C 0x43 -#define VK_KEY_D 0x44 -#define VK_KEY_E 0x45 -#define VK_KEY_F 0x46 -#define VK_KEY_G 0x47 -#define VK_KEY_H 0x48 -#define VK_KEY_I 0x49 -#define VK_KEY_J 0x4A -#define VK_KEY_K 0x4B -#define VK_KEY_L 0x4C -#define VK_KEY_M 0x4D -#define VK_KEY_N 0x4E -#define VK_KEY_O 0x4F -#define VK_KEY_P 0x50 -#define VK_KEY_Q 0x51 -#define VK_KEY_R 0x52 -#define VK_KEY_S 0x53 -#define VK_KEY_T 0x54 -#define VK_KEY_U 0x55 -#define VK_KEY_V 0x56 -#define VK_KEY_W 0x57 -#define VK_KEY_X 0x58 -#define VK_KEY_Y 0x59 -#define VK_KEY_Z 0x5A +#define VK_KEY_A 0x41 +#define VK_KEY_B 0x42 +#define VK_KEY_C 0x43 +#define VK_KEY_D 0x44 +#define VK_KEY_E 0x45 +#define VK_KEY_F 0x46 +#define VK_KEY_G 0x47 +#define VK_KEY_H 0x48 +#define VK_KEY_I 0x49 +#define VK_KEY_J 0x4A +#define VK_KEY_K 0x4B +#define VK_KEY_L 0x4C +#define VK_KEY_M 0x4D +#define VK_KEY_N 0x4E +#define VK_KEY_O 0x4F +#define VK_KEY_P 0x50 +#define VK_KEY_Q 0x51 +#define VK_KEY_R 0x52 +#define VK_KEY_S 0x53 +#define VK_KEY_T 0x54 +#define VK_KEY_U 0x55 +#define VK_KEY_V 0x56 +#define VK_KEY_W 0x57 +#define VK_KEY_X 0x58 +#define VK_KEY_Y 0x59 +#define VK_KEY_Z 0x5A -#define VK_NUMPAD0 0x60 -#define VK_NUMPAD1 0x61 -#define VK_NUMPAD2 0x62 -#define VK_NUMPAD3 0x63 -#define VK_NUMPAD4 0x64 -#define VK_NUMPAD5 0x65 -#define VK_NUMPAD6 0x66 -#define VK_NUMPAD7 0x67 -#define VK_NUMPAD8 0x68 -#define VK_NUMPAD9 0x69 -#define VK_NUMPAD_MULTIPLY 0x6A -#define VK_NUMPAD_ADD 0x6B +#define VK_NUMPAD0 0x60 +#define VK_NUMPAD1 0x61 +#define VK_NUMPAD2 0x62 +#define VK_NUMPAD3 0x63 +#define VK_NUMPAD4 0x64 +#define VK_NUMPAD5 0x65 +#define VK_NUMPAD6 0x66 +#define VK_NUMPAD7 0x67 +#define VK_NUMPAD8 0x68 +#define VK_NUMPAD9 0x69 +#define VK_NUMPAD_MULTIPLY 0x6A +#define VK_NUMPAD_ADD 0x6B -#define VK_NUMPAD_SUBTRACT 0x6D -#define VK_NUMPAD_DECIMAL 0x6E -#define VK_NUMPAD_DIVIDE 0x6F -#define VK_DIVIDE 0x6F +#define VK_NUMPAD_SUBTRACT 0x6D +#define VK_NUMPAD_DECIMAL 0x6E +#define VK_NUMPAD_DIVIDE 0x6F +#define VK_DIVIDE 0x6F -#define VK_NUMLOCK 0x90 +#define VK_NUMLOCK 0x90 -#define VK_LSHIFT 0xA0 -#define VK_RSHIFT 0xA1 -#define VK_LCONTROL 0xA2 -#define VK_RCONTROL 0xA3 -#define VK_LMENU 0xA4 -#define VK_RMENU 0xA5 +#define VK_LSHIFT 0xA0 +#define VK_RSHIFT 0xA1 +#define VK_LCONTROL 0xA2 +#define VK_RCONTROL 0xA3 +#define VK_LMENU 0xA4 +#define VK_RMENU 0xA5 -#define VK_OEM_1 0xBA -#define VK_COLON 0xBA -#define VK_SEMICOLON 0xBA -#define VK_OEM_PLUS 0xBB -#define VK_EQUAL 0xBB -#define VK_OEM_COMMA 0xBC -#define VK_COMMA 0xBC -#define VK_OEM_MINUS 0xBD -#define VK_HYPHEN 0xBD -#define VK_MINUS 0xBD -#define VK_OEM_PERIOD 0xBE -#define VK_PERIOD 0xBE -#define VK_OEM_2 0xBF -#define VK_SLASH 0xBF -#define VK_OEM_3 0xC0 -#define VK_ACCENT 0xC0 -#define VK_GRAVE 0xC0 -#define VK_LEFT_BRACKET 0xDB -#define VK_LBRKT 0xDB -#define VK_OEM_4 0xDB -#define VK_BACKSLASH 0xDC -#define VK_BKSLASH 0xDC -#define VK_OEM_5 0xDC -#define VK_RIGHT_BRACKET 0xDD -#define VK_RBRKT 0xDD -#define VK_OEM_6 0xDD -#define VK_QUOTE 0xDE -#define VK_OEM_7 0xDE -#define VK_xDF 0xDF -#define VK_OEM_8 0xDF -#define VK_OEM_102 0xE2 -#define VK_OEM_CLEAR 0xFE +#define VK_OEM_1 0xBA +#define VK_COLON 0xBA +#define VK_SEMICOLON 0xBA +#define VK_OEM_PLUS 0xBB +#define VK_EQUAL 0xBB +#define VK_OEM_COMMA 0xBC +#define VK_COMMA 0xBC +#define VK_OEM_MINUS 0xBD +#define VK_HYPHEN 0xBD +#define VK_MINUS 0xBD +#define VK_OEM_PERIOD 0xBE +#define VK_PERIOD 0xBE +#define VK_OEM_2 0xBF +#define VK_SLASH 0xBF +#define VK_OEM_3 0xC0 +#define VK_ACCENT 0xC0 +#define VK_GRAVE 0xC0 +#define VK_LEFT_BRACKET 0xDB +#define VK_LBRKT 0xDB +#define VK_OEM_4 0xDB +#define VK_BACKSLASH 0xDC +#define VK_BKSLASH 0xDC +#define VK_OEM_5 0xDC +#define VK_RIGHT_BRACKET 0xDD +#define VK_RBRKT 0xDD +#define VK_OEM_6 0xDD +#define VK_QUOTE 0xDE +#define VK_OEM_7 0xDE +#define VK_xDF 0xDF +#define VK_OEM_8 0xDF +#define VK_OEM_102 0xE2 +#define VK_OEM_CLEAR 0xFE /* * VK__MAX _S2 defines the highest virtual key code defined in the system = 0xFF. Custom VK codes start at 256 */ #define VK__MAX 255 - -} // namespace km_vk \ No newline at end of file +} // namespace vkey +} // namespace km \ No newline at end of file diff --git a/core/src/kmx/kmx_actions.cpp b/core/src/kmx/kmx_actions.cpp index 62d3651ed0..5b4eabe713 100644 --- a/core/src/kmx/kmx_actions.cpp +++ b/core/src/kmx/kmx_actions.cpp @@ -6,7 +6,7 @@ using namespace km::core; using namespace kmx; -using namespace km_vk; +using namespace km::vkey; void KMX_Actions::ResetQueue() { diff --git a/core/src/kmx/kmx_capslock.cpp b/core/src/kmx/kmx_capslock.cpp index 8c02a7559d..4188541171 100644 --- a/core/src/kmx/kmx_capslock.cpp +++ b/core/src/kmx/kmx_capslock.cpp @@ -7,7 +7,7 @@ using namespace km::core; using namespace kmx; -using namespace km_vk; +using namespace km::vkey; /** * Set caps lock state. diff --git a/core/src/kmx/kmx_context.cpp b/core/src/kmx/kmx_context.cpp index 1b573198cc..9c3822a04e 100644 --- a/core/src/kmx/kmx_context.cpp +++ b/core/src/kmx/kmx_context.cpp @@ -6,7 +6,7 @@ using namespace km::core; using namespace kmx; -using namespace km_vk; +using namespace km::vkey; /* KMX_Context */ diff --git a/core/src/kmx/kmx_conversion.cpp b/core/src/kmx/kmx_conversion.cpp index c50091daf5..dacaaef97b 100644 --- a/core/src/kmx/kmx_conversion.cpp +++ b/core/src/kmx/kmx_conversion.cpp @@ -5,7 +5,7 @@ using namespace km::core; using namespace kmx; -using namespace km_vk; +using namespace km::vkey; /* This array is lifted from preservedkeymap.cpp */ diff --git a/core/src/kmx/kmx_debugger.cpp b/core/src/kmx/kmx_debugger.cpp index 22a7281cd6..00261de5f4 100644 --- a/core/src/kmx/kmx_debugger.cpp +++ b/core/src/kmx/kmx_debugger.cpp @@ -10,7 +10,7 @@ using namespace km::core; using namespace kmx; -using namespace km_vk; +using namespace km::vkey; void KMX_DebugItems::push_item( uint8_t type, diff --git a/core/src/kmx/kmx_environment.cpp b/core/src/kmx/kmx_environment.cpp index d01c925ac7..8216791f1a 100644 --- a/core/src/kmx/kmx_environment.cpp +++ b/core/src/kmx/kmx_environment.cpp @@ -9,7 +9,7 @@ using namespace km::core; using namespace kmx; -using namespace km_vk; +using namespace km::vkey; namespace { km_core_cu const diff --git a/core/src/kmx/kmx_file.cpp b/core/src/kmx/kmx_file.cpp index 7946402b66..a6dceb755a 100644 --- a/core/src/kmx/kmx_file.cpp +++ b/core/src/kmx/kmx_file.cpp @@ -8,7 +8,7 @@ using namespace km::core; using namespace kmx; -using namespace km_vk; +using namespace km::vkey; #if defined(_WIN32) || defined(_WIN64) #include diff --git a/core/src/kmx/kmx_modifiers.cpp b/core/src/kmx/kmx_modifiers.cpp index a7f9301c2d..dcf27a9050 100644 --- a/core/src/kmx/kmx_modifiers.cpp +++ b/core/src/kmx/kmx_modifiers.cpp @@ -6,7 +6,7 @@ using namespace km::core; using namespace kmx; -using namespace km_vk; +using namespace km::vkey; #define MAX_RSHIFT 24 #define MAX_KSHIFT 18 diff --git a/core/src/kmx/kmx_options.cpp b/core/src/kmx/kmx_options.cpp index 4a4a4dd221..55f306e0dc 100644 --- a/core/src/kmx/kmx_options.cpp +++ b/core/src/kmx/kmx_options.cpp @@ -8,7 +8,7 @@ using namespace km::core; using namespace kmx; -using namespace km_vk; +using namespace km::vkey; int KMX_Options::_GetIndex(std::u16string const &key) const { auto i = 0U; diff --git a/core/src/kmx/kmx_processevent.cpp b/core/src/kmx/kmx_processevent.cpp index 1dd678ce88..e7dde1b00e 100644 --- a/core/src/kmx/kmx_processevent.cpp +++ b/core/src/kmx/kmx_processevent.cpp @@ -8,7 +8,7 @@ using namespace km::core; using namespace kmx; -using namespace km_vk; +using namespace km::vkey; /* Globals */ diff --git a/core/src/kmx/kmx_processor.cpp b/core/src/kmx/kmx_processor.cpp index 223de78faf..293158a090 100644 --- a/core/src/kmx/kmx_processor.cpp +++ b/core/src/kmx/kmx_processor.cpp @@ -5,7 +5,7 @@ using namespace km::core; using namespace kmx; -using namespace km_vk; +using namespace km::vkey; static KMX_BOOL ContextItemsFromAppContext(KMX_WCHAR *buf, km_core_context_item** outPtr) diff --git a/core/src/kmx/kmx_xstring.cpp b/core/src/kmx/kmx_xstring.cpp index 2a5c83f683..c7c05010f7 100644 --- a/core/src/kmx/kmx_xstring.cpp +++ b/core/src/kmx/kmx_xstring.cpp @@ -10,7 +10,7 @@ using namespace km::core; using namespace kmx; -using namespace km_vk; +using namespace km::vkey; const km_core_cu *km::core::kmx::u16chr(const km_core_cu *p, km_core_cu ch) { while (*p) { diff --git a/core/tests/unit/kmnkbd/debug_api.tests.cpp b/core/tests/unit/kmnkbd/debug_api.tests.cpp index 9b66adcb8a..a7239902eb 100644 --- a/core/tests/unit/kmnkbd/debug_api.tests.cpp +++ b/core/tests/unit/kmnkbd/debug_api.tests.cpp @@ -25,7 +25,7 @@ #include "../load_kmx_file.hpp" using namespace km::core::kmx; -using namespace km_vk; +using namespace km::vkey; km_core_option_item test_env_opts[] = { diff --git a/core/tests/unit/kmnkbd/kmx_context.tests.cpp b/core/tests/unit/kmnkbd/kmx_context.tests.cpp index ef37378c85..5cd9902038 100644 --- a/core/tests/unit/kmnkbd/kmx_context.tests.cpp +++ b/core/tests/unit/kmnkbd/kmx_context.tests.cpp @@ -16,7 +16,7 @@ #include using namespace km::core::kmx; -using namespace km_vk; +using namespace km::vkey; using namespace std; void diff --git a/core/tests/unit/kmnkbd/kmx_xstring.tests.cpp b/core/tests/unit/kmnkbd/kmx_xstring.tests.cpp index 672fbd667e..d909a709ca 100644 --- a/core/tests/unit/kmnkbd/kmx_xstring.tests.cpp +++ b/core/tests/unit/kmnkbd/kmx_xstring.tests.cpp @@ -22,7 +22,7 @@ #include using namespace km::core::kmx; -using namespace km_vk; +using namespace km::vkey; using namespace std; #define U_1F609_WINKING_FACE u"\U0001F609" diff --git a/core/tests/unit/kmx/kmx_external_event.tests.cpp b/core/tests/unit/kmx/kmx_external_event.tests.cpp index bedcb899de..43e2b4501a 100644 --- a/core/tests/unit/kmx/kmx_external_event.tests.cpp +++ b/core/tests/unit/kmx/kmx_external_event.tests.cpp @@ -26,7 +26,7 @@ */ using namespace km::core::kmx; -using namespace km_vk; +using namespace km::vkey; km_core_option_item test_env_opts[] = { diff --git a/core/tests/unit/kmx/kmx_imx.tests.cpp b/core/tests/unit/kmx/kmx_imx.tests.cpp index b9c994b51a..e604126848 100644 --- a/core/tests/unit/kmx/kmx_imx.tests.cpp +++ b/core/tests/unit/kmx/kmx_imx.tests.cpp @@ -27,7 +27,7 @@ */ using namespace km::core::kmx; -using namespace km_vk; +using namespace km::vkey; km_core_option_item test_env_opts[] = { diff --git a/core/tests/unit/kmx/kmx_key_list.tests.cpp b/core/tests/unit/kmx/kmx_key_list.tests.cpp index 62da63d704..dea880c69d 100644 --- a/core/tests/unit/kmx/kmx_key_list.tests.cpp +++ b/core/tests/unit/kmx/kmx_key_list.tests.cpp @@ -18,7 +18,7 @@ #include "../load_kmx_file.hpp" using namespace km::core::kmx; -using namespace km_vk; +using namespace km::vkey; km_core_option_item test_env_opts[] = { diff --git a/core/tests/unit/ldml/kmx_plus.tests.cpp b/core/tests/unit/ldml/kmx_plus.tests.cpp index 30143db7ef..592e697757 100644 --- a/core/tests/unit/ldml/kmx_plus.tests.cpp +++ b/core/tests/unit/ldml/kmx_plus.tests.cpp @@ -11,7 +11,7 @@ #include "utfcodec.hpp" using namespace km::core::kmx; -using namespace km_vk; +using namespace km::vkey; TEST(KMXPlusTest, test_COMP_KMXPLUS_KEYS_KEY) { COMP_KMXPLUS_KEYS_KEY e[2] = { diff --git a/core/tests/unit/ldml/transforms.tests.cpp b/core/tests/unit/ldml/transforms.tests.cpp index dd2aaa9291..fcd9591ccf 100644 --- a/core/tests/unit/ldml/transforms.tests.cpp +++ b/core/tests/unit/ldml/transforms.tests.cpp @@ -42,7 +42,7 @@ using namespace km::core::ldml; using namespace km::core::kmx; -using namespace km_vk; +using namespace km::vkey; void prepend_hex_oct(std::u32string &str, char32_t x) { diff --git a/developer/src/kmanalyze/kmanalyze.cpp b/developer/src/kmanalyze/kmanalyze.cpp index 4d94aeaacd..ed8e7c7d48 100644 --- a/developer/src/kmanalyze/kmanalyze.cpp +++ b/developer/src/kmanalyze/kmanalyze.cpp @@ -9,7 +9,7 @@ #include #include "../../../common/include/km_vkey.h" -using namespace km_vk; +using namespace km::vkey; BOOL LoadKeyboard(LPSTR fileName, LPKEYBOARD *lpKeyboard); diff --git a/developer/src/kmcmplib/src/Compiler.cpp b/developer/src/kmcmplib/src/Compiler.cpp index c04dcc57fa..45a8248e57 100644 --- a/developer/src/kmcmplib/src/Compiler.cpp +++ b/developer/src/kmcmplib/src/Compiler.cpp @@ -121,7 +121,7 @@ #define SUBLANGID(lgid) ((uint16_t)(lgid) >> 10) using namespace kmcmp; -using namespace km_vk; +using namespace km::vkey; KMX_BOOL AWarnDeprecatedCode_GLOBAL_LIB; diff --git a/developer/src/kmdecomp/savekeyboard.cpp b/developer/src/kmdecomp/savekeyboard.cpp index 680d1442c3..ef8e463a1b 100644 --- a/developer/src/kmdecomp/savekeyboard.cpp +++ b/developer/src/kmdecomp/savekeyboard.cpp @@ -38,7 +38,7 @@ // _S2 which version? here from chore/developer/scatteredCode/cleanup_VKeys //#include "../../../common/windows/cpp/include/vkeys.h" #include "../../../common/include/km_vkey.h" -using namespace km_vk; +using namespace km::vkey; //_S2 ======== // _S2 which version? here from master diff --git a/developer/src/samples/imsample/imsample.cpp b/developer/src/samples/imsample/imsample.cpp index c3cc2a6cb4..7bf01da22e 100644 --- a/developer/src/samples/imsample/imsample.cpp +++ b/developer/src/samples/imsample/imsample.cpp @@ -23,7 +23,7 @@ #include "imlib.h" #include "../../../../common/include/km_vkey.h" -using namespace km_vk; +using namespace km::vkey; HWND hwnd; HWND hwndChild; From b780ecc7307d6b77ea642918af8f73ac9671e631 Mon Sep 17 00:00:00 2001 From: Sabine Date: Thu, 2 Jul 2026 12:07:46 +0200 Subject: [PATCH 085/140] chore(common): edit value of VK_DECIMAL --- common/include/km_vkey.h | 8 ++++++-- core/tests/unit/kmnkbd/debug_items.hpp | 2 +- developer/src/kmcmplib/src/Compiler.cpp | 12 ------------ developer/src/kmdecomp/savekeyboard.cpp | 13 ++----------- linux/mcompile/keymap/keymap.h | 3 ++- mac/mcompile/keymap.h | 3 ++- 6 files changed, 13 insertions(+), 28 deletions(-) diff --git a/common/include/km_vkey.h b/common/include/km_vkey.h index 03282ac5d6..78dd6e91a0 100644 --- a/common/include/km_vkey.h +++ b/common/include/km_vkey.h @@ -5,6 +5,9 @@ * VKCodes for keyman */ + +#pragma once + namespace km { namespace vkey { @@ -54,8 +57,9 @@ namespace vkey { #define VK_EXECUTE 0x2B #define VK_SNAPSHOT 0x2C #define VK_INSERT 0x2D -// #define VK_DECIMAL 0x2E // _S2 if I use it here kmdecomp complains because of duplicate macro definition here and in -// WinUser.h +// #define VK_DECIMAL 0x2E // _S2 if I use it here kmdecomp complains because of duplicate macro definition here and in WinUser.h +// #define VK_DECIMAL 0x2E //_S2 probably wrong 0x2E (which is actually the Windows VK_DELETE value, not the standard VK_DECIMAL which is 0x6E for the numpad decimal key) +#define VK_DECIMAL 0x6E #define VK_KEY_0 0x30 #define VK_KEY_1 0x31 diff --git a/core/tests/unit/kmnkbd/debug_items.hpp b/core/tests/unit/kmnkbd/debug_items.hpp index 62e519e40a..41e6e6dfd5 100644 --- a/core/tests/unit/kmnkbd/debug_items.hpp +++ b/core/tests/unit/kmnkbd/debug_items.hpp @@ -6,7 +6,7 @@ #include using namespace km::core::kmx; -using namespace km_vk; +using namespace km::vkey; namespace { diff --git a/developer/src/kmcmplib/src/Compiler.cpp b/developer/src/kmcmplib/src/Compiler.cpp index 45a8248e57..3a66c27d9e 100644 --- a/developer/src/kmcmplib/src/Compiler.cpp +++ b/developer/src/kmcmplib/src/Compiler.cpp @@ -77,20 +77,8 @@ #include "cp1252.h" #include "virtualcharkeys.h" - -//_S2 ======== -//_S2 which version? here from chore/developer/scatteredCode/cleanup_VKeys -// TODO: These three should be under common/cpp/include -- not windows specific -//#include "../../../../common/windows/cpp/include/keymanversion.h" -//#include "../../../../common/windows/cpp/include/crc32.h" -//#include "../../../../common/windows/cpp/include/ConvertUTF.h" -#include "../../../../common/include/km_vkey.h" - -//_S2 ======== -//_S2 which version? here from master #include "../../../../common/include/vkeys.h" #include "../../../../common/include/keymanversion.h" -//_S2 ======== #include "crc32.h" #include "debugstore.h" diff --git a/developer/src/kmdecomp/savekeyboard.cpp b/developer/src/kmdecomp/savekeyboard.cpp index ef8e463a1b..25b180a708 100644 --- a/developer/src/kmdecomp/savekeyboard.cpp +++ b/developer/src/kmdecomp/savekeyboard.cpp @@ -30,20 +30,11 @@ #define _SILENCE_ALL_CXX17_DEPRECATION_WARNINGS #include - #include "../../../common/windows/cpp/include/legacy_kmx_file.h" #include "../../../common/windows/cpp/include/legacy_kmx_memory.h" - - -// _S2 which version? here from chore/developer/scatteredCode/cleanup_VKeys -//#include "../../../common/windows/cpp/include/vkeys.h" -#include "../../../common/include/km_vkey.h" -using namespace km::vkey; - -//_S2 ======== -// _S2 which version? here from master #include "../../../common/include/vkeys.h" -//_S2 ======== + +using namespace km::vkey; #define SSN__PREFIX L"&" diff --git a/linux/mcompile/keymap/keymap.h b/linux/mcompile/keymap/keymap.h index 6dddd05155..ac280ea7b5 100644 --- a/linux/mcompile/keymap/keymap.h +++ b/linux/mcompile/keymap/keymap.h @@ -48,7 +48,8 @@ enum ShiftState { #define VK_DIVIDE 0x6F #define VK_CANCEL 0x03 -#define VK_DECIMAL 0x2E +//#define VK_DECIMAL 0x2E //_S2 probably wrong 0x2E (which is actually the Windows VK_DELETE value, not the standard VK_DECIMAL which is 0x6E for the numpad decimal key) +#define VK_DECIMAL 0x6E #define VK_OEM_CLEAR 0xFE #define VK_LSHIFT 0xA0 diff --git a/mac/mcompile/keymap.h b/mac/mcompile/keymap.h index 92c9c04099..9aabce0fea 100644 --- a/mac/mcompile/keymap.h +++ b/mac/mcompile/keymap.h @@ -41,7 +41,8 @@ enum ShiftState { #define VK_DIVIDE 0x6F #define VK_CANCEL 0x03 -#define VK_DECIMAL 0x2E +//#define VK_DECIMAL 0x2E //_S2 probably wrong 0x2E (which is actually the Windows VK_DELETE value, not the standard VK_DECIMAL which is 0x6E for the numpad decimal key) +#define VK_DECIMAL 0x6E #define VK_OEM_CLEAR 0xFE #define VK_LSHIFT 0xA0 From a8f509d4d76e92255fe867a671d0f9479ce387e6 Mon Sep 17 00:00:00 2001 From: Sabine Date: Thu, 2 Jul 2026 12:33:03 +0200 Subject: [PATCH 086/140] chore(common): include kmx_processevent.h --- developer/src/kmcmplib/src/Compiler.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/developer/src/kmcmplib/src/Compiler.cpp b/developer/src/kmcmplib/src/Compiler.cpp index 3a66c27d9e..395cb36dc2 100644 --- a/developer/src/kmcmplib/src/Compiler.cpp +++ b/developer/src/kmcmplib/src/Compiler.cpp @@ -68,6 +68,7 @@ #include +#include #include "compfile.h" #include #include From 1d616545de5f1a948b28e57fb10430e162a8df6b Mon Sep 17 00:00:00 2001 From: Sabine Date: Thu, 2 Jul 2026 12:55:53 +0200 Subject: [PATCH 087/140] chore(common): include kmx_processevent.h from core --- developer/src/kmcmplib/src/Compiler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/developer/src/kmcmplib/src/Compiler.cpp b/developer/src/kmcmplib/src/Compiler.cpp index 395cb36dc2..e955bd685a 100644 --- a/developer/src/kmcmplib/src/Compiler.cpp +++ b/developer/src/kmcmplib/src/Compiler.cpp @@ -68,7 +68,7 @@ #include -#include +#include "../../../../core/src/kmx/kmx_conversion.h" #include "compfile.h" #include #include From 7d621a8dfb9eafd01514434de1c72a7098e93142 Mon Sep 17 00:00:00 2001 From: Sabine Date: Thu, 2 Jul 2026 13:08:05 +0200 Subject: [PATCH 088/140] chore(common): include km_vkey.h from common --- developer/src/kmcmplib/src/Compiler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/developer/src/kmcmplib/src/Compiler.cpp b/developer/src/kmcmplib/src/Compiler.cpp index e955bd685a..2d3cf2b179 100644 --- a/developer/src/kmcmplib/src/Compiler.cpp +++ b/developer/src/kmcmplib/src/Compiler.cpp @@ -68,7 +68,6 @@ #include -#include "../../../../core/src/kmx/kmx_conversion.h" #include "compfile.h" #include #include @@ -78,6 +77,7 @@ #include "cp1252.h" #include "virtualcharkeys.h" +#include "../../../../common/include/km_vkey.h" #include "../../../../common/include/vkeys.h" #include "../../../../common/include/keymanversion.h" From c38fa4e942a784cefcc6ac3f479e9866f6013e26 Mon Sep 17 00:00:00 2001 From: Sabine Date: Thu, 2 Jul 2026 13:08:05 +0200 Subject: [PATCH 089/140] chore(common): include km_vkey.h from common --- developer/src/kmcmplib/src/Compiler.cpp | 2 +- developer/src/kmdecomp/savekeyboard.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/developer/src/kmcmplib/src/Compiler.cpp b/developer/src/kmcmplib/src/Compiler.cpp index e955bd685a..2d3cf2b179 100644 --- a/developer/src/kmcmplib/src/Compiler.cpp +++ b/developer/src/kmcmplib/src/Compiler.cpp @@ -68,7 +68,6 @@ #include -#include "../../../../core/src/kmx/kmx_conversion.h" #include "compfile.h" #include #include @@ -78,6 +77,7 @@ #include "cp1252.h" #include "virtualcharkeys.h" +#include "../../../../common/include/km_vkey.h" #include "../../../../common/include/vkeys.h" #include "../../../../common/include/keymanversion.h" diff --git a/developer/src/kmdecomp/savekeyboard.cpp b/developer/src/kmdecomp/savekeyboard.cpp index 25b180a708..2b70b7ffbb 100644 --- a/developer/src/kmdecomp/savekeyboard.cpp +++ b/developer/src/kmdecomp/savekeyboard.cpp @@ -33,7 +33,7 @@ #include "../../../common/windows/cpp/include/legacy_kmx_file.h" #include "../../../common/windows/cpp/include/legacy_kmx_memory.h" #include "../../../common/include/vkeys.h" - +#include "../../../common/include/km_vkey.h" using namespace km::vkey; #define SSN__PREFIX L"&" From 106d3eb62dd9257c8270a06b16638bccf0b3abcd Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 2 Jul 2026 15:23:54 +0200 Subject: [PATCH 090/140] chore(android): move v23 styles into v23 resource file Current min Android SDK for Keyman is v21. See: https://developer.android.com/develop/ui/views/theming/themes#Versions --- .../kMAPro/src/main/res/values-night-v23/styles.xml | 6 ++++++ .../kMAPro/src/main/res/values-night/styles.xml | 2 +- .../KMAPro/kMAPro/src/main/res/values-v23/styles.xml | 11 +++++++++++ android/KMAPro/kMAPro/src/main/res/values/styles.xml | 4 ++-- 4 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 android/KMAPro/kMAPro/src/main/res/values-night-v23/styles.xml create mode 100644 android/KMAPro/kMAPro/src/main/res/values-v23/styles.xml diff --git a/android/KMAPro/kMAPro/src/main/res/values-night-v23/styles.xml b/android/KMAPro/kMAPro/src/main/res/values-night-v23/styles.xml new file mode 100644 index 0000000000..eaeeaa087d --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/values-night-v23/styles.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/res/values-night/styles.xml b/android/KMAPro/kMAPro/src/main/res/values-night/styles.xml index 3667b18a9f..d13099886e 100644 --- a/android/KMAPro/kMAPro/src/main/res/values-night/styles.xml +++ b/android/KMAPro/kMAPro/src/main/res/values-night/styles.xml @@ -22,7 +22,7 @@ true @android:color/black - false + true @style/AppTheme.PopupMenu diff --git a/android/KMAPro/kMAPro/src/main/res/values-v23/styles.xml b/android/KMAPro/kMAPro/src/main/res/values-v23/styles.xml new file mode 100644 index 0000000000..0217445182 --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/values-v23/styles.xml @@ -0,0 +1,11 @@ + + + + + + + \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/res/values/styles.xml b/android/KMAPro/kMAPro/src/main/res/values/styles.xml index 78a4a47ff9..9366cd1441 100644 --- a/android/KMAPro/kMAPro/src/main/res/values/styles.xml +++ b/android/KMAPro/kMAPro/src/main/res/values/styles.xml @@ -26,7 +26,7 @@ true @color/toolbarColor - true + true true From 953f687c2abb6089d591a1060626cc2249e8cca2 Mon Sep 17 00:00:00 2001 From: Sabine Date: Thu, 2 Jul 2026 15:40:24 +0200 Subject: [PATCH 091/140] chore(common): comment for VK_DECIMAL, remove VK__MAX; use windows.h for VK_ESCAPE and VK_BACK --- common/include/km_vkey.h | 11 +++-------- core/src/kmx/kmx_processevent.h | 1 - developer/src/samples/imsample/imsample.cpp | 7 +++++-- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/common/include/km_vkey.h b/common/include/km_vkey.h index 78dd6e91a0..8830d22213 100644 --- a/common/include/km_vkey.h +++ b/common/include/km_vkey.h @@ -5,7 +5,6 @@ * VKCodes for keyman */ - #pragma once namespace km { @@ -58,8 +57,9 @@ namespace vkey { #define VK_SNAPSHOT 0x2C #define VK_INSERT 0x2D // #define VK_DECIMAL 0x2E // _S2 if I use it here kmdecomp complains because of duplicate macro definition here and in WinUser.h -// #define VK_DECIMAL 0x2E //_S2 probably wrong 0x2E (which is actually the Windows VK_DELETE value, not the standard VK_DECIMAL which is 0x6E for the numpad decimal key) -#define VK_DECIMAL 0x6E +// #define VK_DECIMAL 0x2E //_S2 probably wrong 0x2E (which is actually the Windows VK_DELETE value, not the standard VK_DECIMAL +// which is 0x6E for the numpad decimal key) +#define VK_DECIMAL 0x6E #define VK_KEY_0 0x30 #define VK_KEY_1 0x31 @@ -159,10 +159,5 @@ namespace vkey { #define VK_OEM_102 0xE2 #define VK_OEM_CLEAR 0xFE -/* - * VK__MAX _S2 defines the highest virtual key code defined in the system = 0xFF. Custom VK codes start at 256 - */ -#define VK__MAX 255 - } // namespace vkey } // namespace km \ No newline at end of file diff --git a/core/src/kmx/kmx_processevent.h b/core/src/kmx/kmx_processevent.h index 53948e0f1c..fef6864cec 100644 --- a/core/src/kmx/kmx_processevent.h +++ b/core/src/kmx/kmx_processevent.h @@ -23,7 +23,6 @@ #include "kmx_options.h" #include "kmx_environment.h" #include "kmx_debugger.h" -#include "../../../common/include/km_vkey.h" /***************************************************************************/ diff --git a/developer/src/samples/imsample/imsample.cpp b/developer/src/samples/imsample/imsample.cpp index 7bf01da22e..61e9489df3 100644 --- a/developer/src/samples/imsample/imsample.cpp +++ b/developer/src/samples/imsample/imsample.cpp @@ -21,9 +21,12 @@ #include #include #include "imlib.h" -#include "../../../../common/include/km_vkey.h" -using namespace km::vkey; +// km_vkey.h is not used here because duplicate definitions of VK codes in km_vkey.h and might +// produce macro redefinition warnings. (in case we ever need VK_ESCAPE or VK_BACK on mac or linux the +// corresponding values are: VK_ESCAPE =0x1B; VK_BACK=0x08) +// #include "../../../../common/include/km_vkey.h" +// using namespace km::vkey; HWND hwnd; HWND hwndChild; From bec72037f11f07a8f681f5805a3243bdc821ee97 Mon Sep 17 00:00:00 2001 From: Mengchou97 Date: Sat, 4 Jul 2026 22:40:36 +0700 Subject: [PATCH 092/140] ondrawerclosed sidebar scroll back to top --- .../main/java/com/tavultesoft/kmapro/MainActivity.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index da48660db5..b991d9642d 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -93,7 +93,7 @@ import androidx.core.view.WindowCompat; import androidx.core.view.WindowInsetsControllerCompat; import androidx.drawerlayout.widget.DrawerLayout; -import androidx.drawerlayout.widget.DrawerLayout; +import androidx.recyclerview.widget.RecyclerView; import androidx.core.content.ContextCompat; @@ -306,6 +306,12 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene @Override public void onDrawerClosed(View drawerView) { textView.callOnClick(); + if (navigationView != null) { + View menuView = navigationView.getChildAt(0); + if (menuView instanceof RecyclerView) { + ((RecyclerView) menuView).scrollToPosition(0); + } + } } }); From 176df82f04896ef2e8e1553dad93d0ae6b96f0d6 Mon Sep 17 00:00:00 2001 From: Mengchou97 Date: Sat, 4 Jul 2026 22:48:14 +0700 Subject: [PATCH 093/140] removed the active keyboard helper in the bottom section as recommended --- .../com/tavultesoft/kmapro/MainActivity.java | 24 ------------------- .../kMAPro/src/main/res/menu/nav_menu.xml | 6 ----- 2 files changed, 30 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index b991d9642d..db77e0c29f 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -1336,8 +1336,6 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene startActivity(new Intent(context, InfoActivity.class)); } else if (id == R.id.nav_palette) { openThemeDialog(); - } else if (id == R.id.nav_about_current_keyboard || id == R.id.nav_help_active_keyboard) { - showCurrentKeyboardSettings(); } return true; @@ -1427,7 +1425,6 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene getString(R.string.drawer_subtitle_about_current_keyboard)); updateThemeDrawerSubtitle(navigationView); updateCurrentKeyboardDrawerSubtitle(navigationView); - updateActiveKeyboardHelpDrawerItem(navigationView); } private void updateCurrentKeyboardDrawerItemTitle(NavigationView navigationView) { @@ -1456,27 +1453,6 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene ); } - private void updateActiveKeyboardHelpDrawerItem(NavigationView navigationView) { - MenuItem menuItem = navigationView.getMenu().findItem(R.id.nav_help_active_keyboard); - if (menuItem == null) { - return; - } - - String keyboardName = getCurrentKeyboardDisplayName(); - if (keyboardName.equals(getString(R.string.drawer_about_no_active_keyboard))) { - menuItem.setTitle(getString(R.string.drawer_title_active_keyboard_help_fallback)); - setDrawerItemSubtitle(navigationView, R.id.nav_help_active_keyboard, keyboardName); - return; - } - - menuItem.setTitle(getString(R.string.drawer_title_active_keyboard_help, keyboardName)); - setDrawerItemSubtitle( - navigationView, - R.id.nav_help_active_keyboard, - getString(R.string.drawer_subtitle_active_keyboard_help, keyboardName) - ); - } - private void updateInstalledLanguagesDrawerTitle(NavigationView navigationView) { MenuItem menuItem = navigationView.getMenu().findItem(R.id.nav_installed_languages); if (menuItem == null) { diff --git a/android/KMAPro/kMAPro/src/main/res/menu/nav_menu.xml b/android/KMAPro/kMAPro/src/main/res/menu/nav_menu.xml index a3bf5b3242..620cdddf51 100644 --- a/android/KMAPro/kMAPro/src/main/res/menu/nav_menu.xml +++ b/android/KMAPro/kMAPro/src/main/res/menu/nav_menu.xml @@ -91,12 +91,6 @@ android:title="@string/drawer_menu_help" app:actionLayout="@layout/nav_right_icon_item" /> - - Date: Sun, 5 Jul 2026 00:09:43 +0700 Subject: [PATCH 094/140] change the keybaord_banner to dark in dark mode --- .../KMAPro/kMAPro/src/main/assets/banner.html | 14 ++++++++++++-- .../main/assets/banner/keyman_banner_dark.png | Bin 0 -> 150866 bytes .../com/keyman/android/BannerController.java | 5 ++++- .../com/keyman/android/SystemKeyboard.java | 12 ++++++++---- .../com/tavultesoft/kmapro/MainActivity.java | 12 ++++++++---- 5 files changed, 32 insertions(+), 11 deletions(-) create mode 100644 android/KMAPro/kMAPro/src/main/assets/banner/keyman_banner_dark.png diff --git a/android/KMAPro/kMAPro/src/main/assets/banner.html b/android/KMAPro/kMAPro/src/main/assets/banner.html index 41107f1142..a159a00eb9 100644 --- a/android/KMAPro/kMAPro/src/main/assets/banner.html +++ b/android/KMAPro/kMAPro/src/main/assets/banner.html @@ -1,8 +1,18 @@ -
+ + +
- + +
diff --git a/android/KMAPro/kMAPro/src/main/assets/banner/keyman_banner_dark.png b/android/KMAPro/kMAPro/src/main/assets/banner/keyman_banner_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..644af21a2dc2dfc2d9e05a620723a345e9ff7685 GIT binary patch literal 150866 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 z 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

@XI;w4h2ZBHm3X!Bw7&4;>=X{yhp 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 literal 0 HcmV?d00001 diff --git a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/BannerController.java b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/BannerController.java index f716faa9aa..5c5a16e597 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/BannerController.java +++ b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/BannerController.java @@ -13,7 +13,7 @@ public class BannerController { public static final String KM_BANNER_DIR = "banner"; public static final String KM_BANNER_THEME_KEYMAN = "banner.html"; - public static void setHTMLBanner(Context context, KMManager.KeyboardType keyboardType) { + public static void setHTMLBanner(Context context, KMManager.KeyboardType keyboardType, boolean isDarkMode) { if (keyboardType == KMManager.KeyboardType.KEYBOARD_TYPE_UNDEFINED) { return; } @@ -22,6 +22,9 @@ public class BannerController { // Always use Keyman banner theme String contents = FileUtils.readContents(context, KM_BANNER_THEME_KEYMAN); + if (isDarkMode) { + contents = contents.replace("class=\"keyman-banner\"", "class=\"keyman-banner dark\""); + } KMManager.setHTMLBanner(keyboardType, contents); KMManager.setBanner(keyboardType, KMManager.BannerType.HTML); KMManager.showBanner(true); diff --git a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java index 8b7d374d9a..c3939c03f8 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java +++ b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java @@ -80,7 +80,7 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven KMManager.setSpacebarText(spacebarText); // Set the system keyboard HTML banner - BannerController.setHTMLBanner(this, KeyboardType.KEYBOARD_TYPE_SYSTEM); + BannerController.setHTMLBanner(this, KeyboardType.KEYBOARD_TYPE_SYSTEM, isDarkMode()); boolean mayHaveHapticFeedback = prefs.getBoolean(KeymanSettingsKeys.HAPTIC_FEEDBACK, false); KMManager.setHapticFeedback(mayHaveHapticFeedback); @@ -176,7 +176,7 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven if (KMManager.getPredictionsSuspended(KeyboardType.KEYBOARD_TYPE_SYSTEM)) { KMManager.setBannerOptions(false); // Set the system keyboard HTML banner - BannerController.setHTMLBanner(this, KeyboardType.KEYBOARD_TYPE_SYSTEM); + BannerController.setHTMLBanner(this, KeyboardType.KEYBOARD_TYPE_SYSTEM, isDarkMode()); } else if (KMManager.isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_SYSTEM)){ // Check if predictions needs to be re-enabled per Settings preference Keyboard kbInfo = KMManager.getCurrentKeyboardInfo(appContext); @@ -282,7 +282,7 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven @Override public void onKeyboardChanged(String newKeyboard) { // Refresh banner theme - BannerController.setHTMLBanner(this, KeyboardType.KEYBOARD_TYPE_SYSTEM); + BannerController.setHTMLBanner(this, KeyboardType.KEYBOARD_TYPE_SYSTEM, isDarkMode()); KMManager.showSystemKeyboard(); sendCurrentFontName(); } @@ -290,7 +290,7 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven @Override public void onKeyboardShown() { // Refresh banner theme - BannerController.setHTMLBanner(this, KeyboardType.KEYBOARD_TYPE_SYSTEM); + BannerController.setHTMLBanner(this, KeyboardType.KEYBOARD_TYPE_SYSTEM, isDarkMode()); } @Override @@ -349,4 +349,8 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven public boolean onKeyLongPress(int keyCode, KeyEvent event) { return interpreter.onKeyLongPress(keyCode, event); } + private boolean isDarkMode() { + return (getResources().getConfiguration().uiMode & + Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES; + } } \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index db77e0c29f..fda31c07ea 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -233,9 +233,8 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { int flags = window.getDecorView().getSystemUiVisibility(); - boolean isDarkMode = (getResources().getConfiguration().uiMode & - Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES; - + boolean isDarkMode = isDarkMode(); + if (!isDarkMode) { flags |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; } else { @@ -710,6 +709,10 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene view.getGlobalVisibleRect(bounds); return bounds.contains((int) ev.getRawX(), (int) ev.getRawY()); } + private boolean isDarkMode() { + return (getResources().getConfiguration().uiMode & + Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES; + } @Override public void onKeyboardLoaded(KeyboardType keyboardType) { @@ -727,7 +730,8 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene @Override public void onKeyboardShown() { // Refresh banner theme - BannerController.setHTMLBanner(this, KeyboardType.KEYBOARD_TYPE_INAPP); + boolean isDarkMode = isDarkMode(); + BannerController.setHTMLBanner(this, KeyboardType.KEYBOARD_TYPE_INAPP, isDarkMode); resizeTextView(true); updateKeyboardToolbarPosition(); if (textPopupToggleButton != null) { From 812fca0d9e3cb14a498a8dc439ea7e481d1e8993 Mon Sep 17 00:00:00 2001 From: Mengchou97 Date: Wed, 8 Jul 2026 13:24:07 +0700 Subject: [PATCH 095/140] resolve the update option from always appearing --- .../com/tavultesoft/kmapro/MainActivity.java | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index fda31c07ea..22c1b87434 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -565,9 +565,20 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene if (menu == null) { return; } - final MenuItem _keyboardupdate = menu.findItem(R.id.action_update_keyboards); - if (_keyboardupdate != null && anUpdateCount > 0) { - _keyboardupdate.setVisible(true); + // 1. Update the Top Action Bar Menu + if (menu != null) { + final MenuItem toolbarUpdate = menu.findItem(R.id.action_update_keyboards); + if (toolbarUpdate != null) { + toolbarUpdate.setVisible(anUpdateCount > 0); + } + } + + // 2. Update the Drawer/Sidebar Navigation Menu + if (navigationView != null) { + final MenuItem drawerUpdate = navigationView.getMenu().findItem(R.id.action_update_keyboards); + if (drawerUpdate != null) { + drawerUpdate.setVisible(anUpdateCount > 0); + } } } @@ -609,8 +620,8 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene public void propertyChange(PropertyChangeEvent evt) { if(!evt.getPropertyName().equals("updateCount")) return; - updateUpdateCountIndicator( - evt.getNewValue()==null?0:(Integer) evt.getNewValue()); + int count = KMManager.getUpdateTool().getOpenUpdateCount(); + updateUpdateCountIndicator(count); } } ); @@ -647,11 +658,6 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene KMManager.getUpdateTool().executeOpenUpdates(); // Dismiss icon updateUpdateCountIndicator(0); - final MenuItem _keyboardupdate = menu.findItem(R.id.action_update_keyboards); - if (_keyboardupdate != null && _keyboardupdate.isVisible()) { - _keyboardupdate.setVisible(false); - } - return true; } else if (item.getItemId() == R.id.action_sidebar) { if (drawerLayout.isDrawerOpen(GravityCompat.END)) { @@ -1316,10 +1322,6 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } else if (id == R.id.action_update_keyboards) { KMManager.getUpdateTool().executeOpenUpdates(); updateUpdateCountIndicator(0); - final MenuItem keyboardUpdate = navigationView.getMenu().findItem(R.id.action_update_keyboards); - if (keyboardUpdate != null && keyboardUpdate.isVisible()) { - keyboardUpdate.setVisible(false); - } } else if (id == R.id.action_settings) { showSettings(); } else if (id == R.id.nav_installed_languages) { @@ -1340,8 +1342,9 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene startActivity(new Intent(context, InfoActivity.class)); } else if (id == R.id.nav_palette) { openThemeDialog(); + } else if (id == R.id.nav_about_current_keyboard) { + showCurrentKeyboardSettings(); } - return true; } From 681fb1248a036e844c4f477418e0fe97a9eebc39 Mon Sep 17 00:00:00 2001 From: Mengchou97 Date: Wed, 8 Jul 2026 15:29:50 +0700 Subject: [PATCH 096/140] resolved the logo overlayed in arabic language --- android/KMAPro/kMAPro/src/main/res/drawable/keyman_logo_mode.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/keyman_logo_mode.xml b/android/KMAPro/kMAPro/src/main/res/drawable/keyman_logo_mode.xml index 35a6f8acd2..63182c6c53 100644 --- a/android/KMAPro/kMAPro/src/main/res/drawable/keyman_logo_mode.xml +++ b/android/KMAPro/kMAPro/src/main/res/drawable/keyman_logo_mode.xml @@ -9,6 +9,7 @@ Date: Wed, 8 Jul 2026 22:41:50 +0700 Subject: [PATCH 097/140] resolved the navigation bar layover keyboard problem --- .../java/com/tavultesoft/kmapro/MainActivity.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index 22c1b87434..c9b5b88abb 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -183,7 +183,11 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene AppCompatDelegate.setDefaultNightMode(savedMode); getDelegate().applyDayNight(); super.onCreate(savedInstanceState); + KMManager.initialize(getApplicationContext(), KeyboardType.KEYBOARD_TYPE_INAPP); setContentView(R.layout.activity_main); + constraintLayout = (ConstraintLayout) findViewById(R.id.constraintLayout); + + setupEdgeToEdge(R.id.constraintLayout); context = this; checkSendCrashReport(); @@ -201,8 +205,6 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene KMManager.setDebugMode(true); } - // Verify WebView installed and enabled before attempting to initialize KMManager - KMManager.initialize(getApplicationContext(), KeyboardType.KEYBOARD_TYPE_INAPP); KMManager.executeResourceUpdate(this); @@ -214,15 +216,12 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene checkHapticFeedback(); - setContentView(R.layout.activity_main); - NavigationView navView = findViewById(R.id.nav_view); DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); int width = (int) (displayMetrics.widthPixels * 0.80); navView.getLayoutParams().width = width; navView.requestLayout(); - constraintLayout = (ConstraintLayout) findViewById(R.id.constraintLayout); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { Window window = getWindow(); window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); @@ -326,6 +325,11 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene initializeKeyboardToolbar(); + textView.post(() -> { + KMManager.onConfigurationChanged(this.getResources().getConfiguration()); + KMManager.getKeyboardHeight(this); + }); + // Check WebView enabled and Chrome version SystemWebViewStatus webViewStatus = WebViewUtils.getSystemWebViewStatus(context); EngineWebViewVersionStatus chromeStatus = KMManager.getEngineWebViewVersionStatus(); From c243ceab3ab74429ac3021d129dd57e32317c35c Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 10 Jul 2026 15:54:03 +0200 Subject: [PATCH 098/140] docs(android): update keyboardharness readme Test-bot: skip Build-bot: skip --- android/Tests/KeyboardHarness/readme.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/android/Tests/KeyboardHarness/readme.md b/android/Tests/KeyboardHarness/readme.md index 898d992638..6d63e5d883 100644 --- a/android/Tests/KeyboardHarness/readme.md +++ b/android/Tests/KeyboardHarness/readme.md @@ -13,15 +13,9 @@ and consists of these engineering keyboards: test9469.kmp is another engineering keyboard to show non-printing characters on the OSK. ### Compiling From Command Line -1. Launch a command prompt to the `android/` folder -2. Compile KMEA. This will build and copy `keyman-engine.aar` to the Samples and Test projects - ``` - cd KMEA - ./build.sh - ``` +1. Launch a command prompt to the `android/Tests/KeyboardHarness` folder 3. Compile KeyboardHarness ``` - cd ../Tests/KeyboardHarness ./build.sh ``` From 65b93168404c3ab38a62c7a1467480c23e09a253 Mon Sep 17 00:00:00 2001 From: "mengchouchey@gmail.com" Date: Sun, 12 Jul 2026 15:28:39 +0700 Subject: [PATCH 099/140] updated sidebar - removed checkboxes options --- .../kmapro/GetStartedActivity.java | 2 +- .../kmapro/KeymanSettingsFragment.java | 2 +- .../com/tavultesoft/kmapro/MainActivity.java | 33 +--- .../kMAPro/src/main/res/menu/nav_menu.xml | 166 +++++++++--------- .../kMAPro/src/main/res/values/strings.xml | 7 +- 5 files changed, 95 insertions(+), 115 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/GetStartedActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/GetStartedActivity.java index 7f95881901..821b042e26 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/GetStartedActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/GetStartedActivity.java @@ -101,7 +101,7 @@ public class GetStartedActivity extends BaseActivity { hashMap = new HashMap(); hashMap.put(iconKey, "0"); - hashMap.put(textKey, getString(R.string.enable_system_keyboard)); + hashMap.put(textKey, getString(R.string.system_keyboard)); hashMap.put(isEnabledKey, "true"); list.add(hashMap); diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsFragment.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsFragment.java index cc31c8ab26..e15c444887 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsFragment.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsFragment.java @@ -75,7 +75,7 @@ public class KeymanSettingsFragment extends PreferenceFragmentCompat { }; setSystemKeyboardPreference = new CheckBoxPreference(context); - setSystemKeyboardPreference.setTitle(R.string.enable_system_keyboard); + setSystemKeyboardPreference.setTitle(R.string.system_keyboard); setSystemKeyboardPreference.setSingleLineTitle(false); setSystemKeyboardPreference.setDefaultValue(SystemIMESettings.isEnabledAsSystemKB(context)); setSystemKeyboardPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index c9b5b88abb..4c51811a86 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -281,8 +281,6 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene updateInstalledLanguagesDrawerTitle(navigationView); initializeDrawerItemSubtitles(navigationView); initializeDrawerToggleOptions(navigationView); - initializeDrawerCheckboxOptions(navigationView); - refreshDrawerSystemKeyboardCheckboxes(navigationView); drawerLayout.addDrawerListener(new DrawerLayout.SimpleDrawerListener() { @@ -441,7 +439,6 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene super.onResume(); if (navigationView != null) { - refreshDrawerSystemKeyboardCheckboxes(navigationView); updateCurrentKeyboardDrawerItemTitle(navigationView); updateInstalledLanguagesDrawerTitle(navigationView); initializeDrawerItemSubtitles(navigationView); @@ -1319,10 +1316,8 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } private boolean handleDrawerNavigationItemSelected(int id) { - if (id == R.id.nav_checkbox_enable_system_keyboard) { + if (id == R.id.system_keyboard) { startActivity(new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS)); - } else if (id == R.id.nav_checkbox_set_default_keyboard) { - showDefaultKeyboardPickerFromMainPage(); } else if (id == R.id.action_update_keyboards) { KMManager.getUpdateTool().executeOpenUpdates(); updateUpdateCountIndicator(0); @@ -1592,32 +1587,6 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene menuItem.setTitle(fullTitle); } - private void initializeDrawerCheckboxOptions(NavigationView navigationView) { - bindDrawerCheckboxAction(navigationView, R.id.nav_checkbox_enable_system_keyboard, - () -> { - if (drawerLayout != null) { - drawerLayout.closeDrawers(); - } - startActivity(new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS)); - }); - - bindDrawerCheckboxAction(navigationView, R.id.nav_checkbox_set_default_keyboard, - this::showDefaultKeyboardPickerFromMainPage); - } - - private void showDefaultKeyboardPickerFromMainPage() { - Intent pickerIntent = new Intent(this, GetStartedActivity.class); - pickerIntent.putExtra(GetStartedActivity.openDefaultKeyboardPickerOnlyKey, true); - startActivity(pickerIntent); - } - - private void refreshDrawerSystemKeyboardCheckboxes(NavigationView navigationView) { - setDrawerCheckboxState(navigationView, R.id.nav_checkbox_enable_system_keyboard, - SystemIMESettings.isEnabledAsSystemKB(context)); - setDrawerCheckboxState(navigationView, R.id.nav_checkbox_set_default_keyboard, - SystemIMESettings.isDefaultKB(context)); - } - private void bindDrawerSwitch(NavigationView navigationView, int menuItemId, boolean defaultValue, final SwitchChangeHandler switchChangeHandler) { MenuItem menuItem = navigationView.getMenu().findItem(menuItemId); diff --git a/android/KMAPro/kMAPro/src/main/res/menu/nav_menu.xml b/android/KMAPro/kMAPro/src/main/res/menu/nav_menu.xml index 620cdddf51..a2589abff7 100644 --- a/android/KMAPro/kMAPro/src/main/res/menu/nav_menu.xml +++ b/android/KMAPro/kMAPro/src/main/res/menu/nav_menu.xml @@ -3,97 +3,103 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity"> + + +

+ + - - + - + - + + + + + + + - + - - + - - + - + - + - + - + - + + + + - + + + + - - - - - - - - + + + + diff --git a/android/KMAPro/kMAPro/src/main/res/values/strings.xml b/android/KMAPro/kMAPro/src/main/res/values/strings.xml index 066daa5c0a..e5892b084a 100644 --- a/android/KMAPro/kMAPro/src/main/res/values/strings.xml +++ b/android/KMAPro/kMAPro/src/main/res/values/strings.xml @@ -13,6 +13,11 @@ Web Browser + + Setup + Preferences + Help + Show keyboard name on spacebar @@ -94,7 +99,7 @@ Add a keyboard for your language - Enable Keyman as system-wide keyboard + System keyboards Set Keyman as default keyboard From 62c85e37290b5c30c472a4e2274625bb614a683d Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Mon, 13 Jul 2026 16:03:31 +1000 Subject: [PATCH 100/140] fix(windows): add required download case add required download message in lieu of download status bar. Also removed red warning background colour and add a caution icon. --- ...yman.Configuration.UI.UfrmStartInstall.dfm | 1141 ++++++++++++++++- ...yman.Configuration.UI.UfrmStartInstall.pas | 45 +- .../main/Keyman.System.UpdateStateMachine.pas | 15 + windows/src/desktop/kmshell/main/UfrmMain.pas | 31 +- windows/src/desktop/kmshell/main/initprog.pas | 2 +- .../src/desktop/kmshell/xml/caution-icon.png | Bin 0 -> 32615 bytes windows/src/desktop/kmshell/xml/strings.xml | 5 + 7 files changed, 1192 insertions(+), 47 deletions(-) create mode 100644 windows/src/desktop/kmshell/xml/caution-icon.png diff --git a/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.dfm b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.dfm index 9bd3818631..ea0fa43a54 100644 --- a/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.dfm +++ b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.dfm @@ -4,8 +4,8 @@ object frmStartInstall: TfrmStartInstall BorderIcons = [biSystemMenu] BorderStyle = bsDialog Caption = 'Keyman Update' - ClientHeight = 240 - ClientWidth = 435 + ClientHeight = 350 + ClientWidth = 586 Color = clBtnFace Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText @@ -18,10 +18,10 @@ object frmStartInstall: TfrmStartInstall PixelsPerInch = 96 TextHeight = 13 object lblUpdateMessage: TLabel - Left = 64 - Top = 89 - Width = 303 - Height = 34 + Left = 32 + Top = 80 + Width = 392 + Height = 17 Caption = 'An update to Keyman has been downloaded and is ready to install.' Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText @@ -31,6 +31,25 @@ object frmStartInstall: TfrmStartInstall ParentFont = False WordWrap = True end + object lblDownloadRequired: TLabel + Left = 32 + Top = 144 + Width = 523 + Height = 65 + AutoSize = False + Caption = + 'The update has not yet been downloaded. Keyman Configuration wil' + + 'l close, and it will be downloaded in the background before cont' + + 'inuing installation.' + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -13 + Font.Name = 'Segoe UI' + Font.Style = [] + ParentFont = False + Visible = False + WordWrap = True + end object imgKeymanLogo: TImage Left = 8 Top = 8 @@ -146,23 +165,16 @@ object frmStartInstall: TfrmStartInstall 414B8B49C3A0A5C5A461D0D262FA3F82D7F60E256D51F10000000049454E44AE 426082} end - object shpMeteredWarning: TShape - Left = 60 - Top = 149 - Width = 314 - Height = 45 - Brush.Color = 15132415 - Pen.Color = clRed - Visible = False - end object lblMeteredWarning: TLabel - Left = 64 - Top = 150 - Width = 303 - Height = 43 + Left = 88 + Top = 232 + Width = 471 + Height = 65 AutoSize = False - Caption = 'Metered Warning' - Color = 15132415 + Caption = + 'You\'#39're on a metered connection. Downloading now may incur data ' + + 'charges' + Color = clBtnFace Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText Font.Height = -13 @@ -174,9 +186,1088 @@ object frmStartInstall: TfrmStartInstall Visible = False WordWrap = True end + object imgCaution: TImage + Left = 32 + Top = 240 + Width = 38 + Height = 38 + Picture.Data = { + 0954506E67496D61676589504E470D0A1A0A0000000D49484452000002000000 + 02000806000000F478D4FA000000017352474200AECE1CE90000000467414D41 + 0000B18F0BFC6105000000097048597300000B1200000B1201D2DD7EFC000085 + 334944415478DAEC9D07981455D686BF8ADD93077042F7CC8039A7155131AE39 + EBAA2098104454A20AA2828092150524A382202A2866D135E71C7ED3AE189675 + 8599E99E21A761A6ABAAABFE7BBBAB917595383375BBEBBCCFD3F6D0124E7757 + DDFBDD7BCF778E0482200882207C87E475000441100441343F240008822008C2 + 87900020088220081F420280200882207C08090082200882F021240008822008 + C287900020088220081F420280200882207C08090082200882F0212400088220 + 08C287900020088220081F420280200882207C08090082200882F02124000882 + 2008C287900020088220081F420280200882207C08090082200882F021240008 + 822008C287900020088220081F420280200882207C08090082200882F0212400 + 08822008C287900020088220081F420280203290A2A2A25C59968B24496AC59E + FFCA5F721C27CC1E7BB2D762B6ED189A86DD6519BBF3DF6FDBF8D534F1AB2C4B + 3AFB3D01F67BFECD1E51F67315FBF3D1783CBEA265CB969F2C5EBCD8F0FABD11 + 04D1389000208834E7C0030FD4D7AE5DDB964DD647B25F1EC99E4F5514A58C3D + 37FABFA528FCBFCEBBA6293FC17EFEB6A0A0E02B120504919E9000208834834F + F8AB57AF3E94FDD881ADD27BB1C93E6F6B93FDAE0A01F66F6CF5FFF1DD01F6E3 + 07ECDF79BCA6A6E659AF3F1F8220B60F1200049126949484CFD334E74E59968F + 8CC7FFF7FF37C58A7F6BFC9130E03B04B61DFF301E979E61713E5D5D5D5DE5D5 + E74510C4D62101401002C3277D5D77864992DCEEF7937E734FF8DBE2F7828009 + 005896F51D7B1EC484C0DFBD8E8F2088FF860400410846494949319B346F633F + 5EAB284AFE9613BD6893FED6480902F798603DFBF121DBB6C7D5D6D62EF73A36 + 822048001084301414B429CCCB331E6093FEA596959E93FE1FB1E5CE00DF1560 + 62E007F6E8C884C0F75EC746107E86040041780C5BF11FC426FDA7D8E300B642 + 4EBC96EE93FE9F911203EEF1C0B7EC7D0EAAA9A979C5EBB808C28F900020088F + D873CF16050D0D5973D9C4FFB74C9FF87FCF9642201E8F2F66EFBF2313028BBD + 8E8B20FC040900826866F8567F7E7E6C862CAB9D535BFD7E99F87F4F4A083011 + C477049ECDCE6EE8B664C9EAF55EC745107E800400413423A150E83A36D94F56 + 5535C0277D77E2E7FFF1F5BD9812029AC67704ACE19595D1BBBC8E8920321D5F + 0F3A04D15C9496961EC87DF1A9737EBFAEF8B74242047121B0C5B140879A9A9A + 1FBC0E8C203215120004D1C4545484EE5214F54EC3E0E7FC34F16F0377374482 + AE73FBA0B560E3C6EC9E6BD6FCB2CEEBC00822D3200140104D045BF51FC056B3 + CFD0AA7FE7A0DD0082685A4800104413100A857AB0097F0A3FEB7733FC7D7FCE + BFF3F0DD0019B66DDEB96C59CD08AFA321884C8106248268445AB56A95170804 + E6B289FF62B672A5557F2391DC0D50F891C033B158ACDBAA55AB36781D1341A4 + 3B240008A29170B7FC79A2DF81F13FEAD643EC0A891D146E17E447024C585D12 + 8D467FF43A288248674800104423100E874F6393D2EB6C829268CBBFC9487CA6 + 6E15C19824497D980898E575500491AED0004510BB48EBD6A5C364591B4E59FE + CDC9E6BC8061CB96D58CF43A1A824847480010C42E100A8546699A768765F12D + 7F9AFC9B1B5555619AE6A868343AD4EB580822DD200140103B415151512E9BF8 + 79B2DF259665F19768CBBFF9497CE6AE0878DAB6EDAEB5B5B5755E074510E902 + 0D5804B183A4267F45512E7193FD68F2F70E120104B193D0A045103B004DFE42 + B2D921605916890082D84E68E02288ED844FFE6CA539873D3AD0E42F1C240208 + 6207A1C18B20B6039AFCD3023A0E20881D80063082D80634F9A715FF25021445 + E952555555EF7550042122348811C456088542D99224CDA36CFFB4624B113022 + 1A8DDEE97540042122349011C45660026084A6694369F24F3B480410C436A0C1 + 8C20FE049AFCD31E87B7110A0412A58307575646C77A1D104188040D6804F107 + B46913EE2CCBCA82582C51DE9726FFF4C5911876B241C35FA3D1E8075E074410 + A240831A41FC8ED2D2D213D99CF11E6F3AE33834F967000EFB2EB908F85E5194 + 0B2B2B2BFFED754004210234B011C416545454EC158FC75F6013C6416CC2C888 + C99F4B18FE4E1CB606E6EBE0C4BB72DB16A4BA176C7E93EC07993F64DE762FF9 + B394F69F4012B746C053ECC7AED1687493D7F11084D764C8AD4D10BB0ECFF867 + 4F8F6C61F74B5B78F889879DFC359BFBA0C8C9899DFFCC27752E0C647704B09D + DF5EE37F8E0B05FE67531F83E2FE39FE4867DCA4C0E14C00DCE5752C04E13524 + 0008C2850980E19AA60D7393FED20E3E799B5672F2E613B5A62627F0D2121B47 + 1C6AE3A0FD1D1C7C808D430FB4D1AAA583FA1850D00A896D8075AB81AC00B072 + B584EF16CBF8FE07F6F851C257DFC9A8A99521B3BF8F7F2CFCC18582AE25FF0D + 27BD1A202676740281C44EC0A0CACAE8DD5E0744105E4202802018BBEF1EEE24 + 49CA13B158FA15FAE193BC69267E74D8E42C15EDE6E082B32D9C76928DF647DA + C82D676FC79292EFCA761FA989FBF76700FC59761FFC67D5C1862A099F7E29E3 + 8D7765BCF49A8A554C24C46D36F7B3BF47D378F95DAF3F811D229514E8301170 + FC8A152B3EF63A2082F08AB419E408A2A90887C3C73A8EF351BA25FDA556E47C + 02B61DA7E1F083E34ED7CBECAC0E17C6112C616F23C6DE06D733BF9FF0530F09 + FF2D0052AFA51EA9D7B918E093BCCEFF51073F7C2363F83D4AFDAB6FAA92A64B + 413BCEB7D6938F34C1511445620260217BEE4A950209BF9216031D4134152525 + 25396CE27F5855D54BE3F1B8F0933FDF7EE72B7EC34C6EBF97163B38FDAF1606 + F48EA3F5C16CA637D86FE027187CE2DF72B2DFA57F748B474A0C041D2CFBA78C + FBA62978F31D15352BA4B43B1AA07C00C2EF083DD8114453C356FFC3D844309C + AD06859FFCF9A46A18FC9C5FC26EAD6CF4B8CAC4ADFDE2905BB2FF51EF4EFCA9 + 6DFEA6242504F88A3FCB417C9584719355CC7A4CC5CA553234D581AE0BEF1ED8 + 9C0F60DBE6254B97D63CEB754004D1DC887D8B124413525454742C9BFC3FE41E + 7147E0252B9F482D9ED1CF26F8FA0609A79D6461C67803A1FD58CC75D2FF9EEB + 375B60F82D5F20C741E427093D07E878EB3D155941070A3F16107B3760737D80 + 783C7E5E6D6DEDAF5E074410CD090900C297A4D3D63F5FF5D76D9270C8813646 + DD61E0F4BFC5935BFD0D68FAD5FEF6C24540903D74076F3CAF60C8681DFF582C + 23273BB91B20300EBB06783EC05D914864B8D7C110447322ECA047104D493A6C + FD27B6FC4D60139BFCCF38C5C2F47B0D84F7672F6E947E3BE31709FE29F2FC80 + 5C07D53F48E83550C71BEFA8C8E6224013FB48207914605DBC7469F439AF6321 + 88E642E05B92209A86A2A2F0B1AAEA08BBF59FDAF2AFAF97909F6763F8ED06AE + E9EDAEFA79BEBA7821FFEE0DB0471612BB01B3A72AB8EB1E1DEB37C8C8CA7244 + 3D12D8F228E0FCDADADAFF781D104134072400085F51545494CB56FEB3D8A393 + 885BFF7CF2E79EFE8D75120ED8D7C634B6EA3FFA5436F96F90BC39E7DFE93782 + E4B1409E83CFDE54D0EB561D3FFD4B466E8E93B00B8A2802DCA38021914864B4 + D7C110447320D4E047104D4D4545F80255555E700BFE08456AF25FB73EB9E53F + 73BCBBE5BF4E4A9F89FF7FDE147B143888FC28A1C74D3ADE7857450BF66B4D13 + 4F0448EC0BE00582D8A37D6D6DED675EC743104D0D0900C237B46CD9323F1008 + 3CA8286A27DB164B00A4267F5E8AF79A2B2C3C34D948AC9EB13E8D27FFCD6F0E + 091150C5F30206E878FD1D1505F9428A80C42E80699A7744A3D1315E0743104D + 0D0900C2378442A1419AA68DB12C8B6FA6CB5EC793E27F26FF496CF2CF65FF63 + 83D7913532F9ECB111B8B6AF8E39F355ECD6523C11E0EE02D8F178FCB8E5CB97 + 7FEA753C04D1949000207C414949C91EB22CBFA028CA21B62D8A77EE4F56FEBC + 27E146AF236B22F292EFEDDA7EC28A805499E027D9CFD750DB602293210140F8 + 8270383C4455D59122D9FE12D9FE1612CD75BA5EC626FF29193EF9A7E0BB1B75 + 4911F0C80235D19950B4C440D71678D1D2A5D1E7BD8E85209A0A21064282684A + 8A8BC3ED1525D1EC4718DB5F6AF25FB14AC2D59D2D3C3CCD27937F0A2E02D8DA + BA5B2F1D8F2E5451D4CA812CCCA10CC01B43912D90C874480010194D2814CA66 + 63F91C5916ABE21F3F8558BB4EC229275A78608281F20359686B8508ADF92874 + 50B558C2B53706F0DE470A5AB05F0B542C886C8144C623CEED46104D409B36A1 + 8BD8E4FFAC48B63FBE09B1864DF6271D17C7C3536209AB9FB3461269F26BB6CF + 416AE1E0E3D715F4BC45C7BFFF2327DC01A2E02604F216D1ED6A6A6ABEF43A1E + 82686C7C36E4107EA2A2A2622FB6EA7F5196E5034549FCE3933CF7F9EFB5BB9D + 68E8D3FEF4B8FF56FEBF87ADFCE7CF563060880EC394100C781DD06652BB0077 + 462291115E0743108D8DCF471E2293098542B7699A76B728897F7CF2E74D7D34 + CDC6E4B1063A5F43937F02DE3F20CFC18C892A86DFA3C3642280970D16245D03 + C1A00ACB8A9DBF6C59ED4B5EC742108D098D3E444622A2ED2FCEC258B152C22D + 7D4CDC3331066C90934D7D7C4EE22820873DD7015D7BEB98FF4CD21E284A52A0 + 2C2B88C7AD85ECC76E640B24320912004446229AED8F4F72CBD9E47FF92516E6 + 3D600001F6DA26B13BE43527A97C805452E0079F28282C10640B8085A7691A3F + 0AE8585D5DFDB4D7C110446341C30F91711417171FC356FE9F702B9728B6BF0D + 1B25ECB5878DA9F71838EECCB82F93FEB6454A047CF89AC24B063BBF56CAC8CF + 732411BE42D716B8381E8F9F5B5B5BFBABD7F1104463404310915170DB1F7B7A + 84ADFE3B8862FB6B8801AAEA60FC0813575E67D1E4BF35DC7C80DB6E5637DD37 + 55578A76434011E328806C8144C641C3109151949595756403F542D3348598FC + F9EA959FFB77BAD8C2A3D30D485C9E34781D95B82476010A80653F48B8EE661D + 1F7C2CCE5140CA16C81EC750B7402213F07C802488C6A2A4A4647745515E16C9 + F6B7D1DDFA9F425BFFDB4DEA28E0A3D714DC3040C7D26532F2F385700524FA04 + D8B6B5D030ACEE2B56ACF04BDD462243A1A188C818444BFC8B1980A63A1837DC + C455B4F5BF63248E026CDCD42B80A90F69282E12A74A60B24F8079CED2A535AF + 781D0B41EC0A82DC5204B16B8898F8C7B7FE3B5E68E1B1070C4859A0ADFF1D20 + 751450F903D0FDC6003EFC24592A5804C81648640A240088B4A7A8A82857D7D5 + 876559ED2852E29FA639B87B9889AE3D69F5BF33A48E021E99A1E2F6111A4C4B + 4240F73AAA04B6A669722C16EB505353F38CD7C110C4CE42431291F6B469537A + 8E2C6B2F8B54EF7FE56A0997B2D5FF23D30DC87CF51FF33AA23485D74BA8072E + BF5EC7338B925D0345C0B505FEC004E739640B24D2151200445AE3DAFEE62A8A + DAD1B6BD1700A95AFF7BB4B631FD3E03C79F45897FBB426A1760CE741583466A + 8932C182F40A205B2091F6D0B044A4356565651DD840FC94699A3CEDDF73C778 + AAE25FDF1E26264E63CBFEF52C24310C09E90B9BF0ED7AA04B4F1D4FBFA8A255 + 4B317601B6B0051E5D5B5BFBB9D7F110C48E420280485B5CDBDFDF65593E4014 + DB1F6FF37B42FB38664F8EA17C7F2608D651B9DF5D25B50BF0C12B0A7A0DD4F1 + EBB264DB6001723D53B6C0A72449B9BAAAAAAADEEB80086247A0A189485B44B4 + FDE99A83B1C34C5C7D0325FE352A7C6F273F690B9C364BC36EBB399005F96CB9 + 2DD0B2E2175656465EF43A1682D81104B9850862C760ABFFA31545FE5492C4B0 + FDF11BA976A5844BCEB7F0C4434C090441897F8DC8665BE08F40F77E62D90293 + D760222190F709F88FD7F110C4F6420280483BCACBCBB36C3B3E4F5104AAF7DF + C0FBC63BB8FB4E2AFAD354A48E02E6CE503178A406C314C616984A081C188944 + EEF33A1882D85E688822D28E8A8AD0856CC07D5E24DB5FED0A09BDBB9B983483 + 12FF9A14EE006062ABD3B53A9E7B4945F16E0E44D807706D81FF648FF36A6A6A + 967A1D0F416C0F240088B4A2B8B8784F36F9BF2C49F2FE8EE3FD2CEBDAFE9C3D + DA245AFD4A279E43B6BFA624B50BF0E803C9E2400D0D128241AFA34A8646B640 + 22DDA0618A482BC2E1F04036D08E1325F18F9B0F56AD718C9B7A9AD6B8895636 + D64BB4FA6F6ADC84C0BED70730638E86922211F60092B6402E4AE371B20512E9 + 81E70328416C2FA5A5A56D6459E6DDFE0E12CEF6372586F2FDC8F6D71CA47601 + DEFFBB823EB7E90EB705E6E7399200B9A0095B603C6E3D2DCB4A17B20512A243 + 43159136B0D5FF1D6CF53F4A94D53FAFF7CF6D7F3CF18F6C7FCD4C6217C0C12D + 37A99BA6CCD4D4962D245DF6BC0C549260508169C62FA8AC8C2CF23A1682D81A + 345C1169414949C9516C75F559729BD5FBA51E67159BF03B9C6FE1D1990624B2 + FD352B295B60D58FC0357D03F8E833058505625C17AE2DF047B74F00D9020961 + 210140084FD2F6673FCA04C02522D8FE52F5FE776F6D6306D5FBF78C2D6D8183 + 46246D8182F509B82512898CF73A1882F83368C82284A7A2227C81A6292F3434 + 8861FBE3130F5FFDDFD0CDC4A4E964FBF314DE2DB001B8F27A1DCFBEA4A2650B + 3176015C5BE0F78EE39C138D4697791D0F41FC11240008A1E1897F9224BD26CB + CA7EA2D8FE56AE929CF647C79DC767C6E4F0BE94F8E7255BF609E873AB8EFF2C + 9391972B8408205B20213C346C1142235AE25F3C0EAC5DE718C3069AD6E01156 + 366DFD0B806B0BECD5238087E66989E2402290CA57B16DFBA89A9A9A2FBC8E87 + 207E0F0D5D84B08898F8B7625532F16FFEC306A0B0170CAF232212CAB070B32D + D0FE75A92CE5E78B640B8C3FA369DA954B972E6DF03A2082D81212008490B0C9 + 3F870D9E8F8892F8C749D9FEC60C35D1AD17D9FE8422650BBC51DD34E5414D6D + 5928962DD0B28CF3972DAB7DC9EB5808624B68F8228464F7DD4367C8B2FA9A28 + 897F9CD56CC2BF98ADFE1F9F69246BD2D3EA5F1852B6C0EA9F806E7DC4B305DA + 76FC67F6E3E99410488804090042385CDBDF63B22C5F2C42C53FB2FDA507A2DB + 024DD3BC830980BB419E11421068082384A3B4B4F4E24020F00C1B30F940E9F9 + 466EB2DEBF849EDDA8DB9FF0F0099F7D45975FA7E3B99755B42C1465172099C7 + 6259D6A1CB972FFF87D7F110048704002114A150A8357B7A83ADFEF71525F16F + ED3A09C71D13C7DCA93190ED4F6C52BB001FBEAAA0E7001DBF56CA28C87720C0 + A594D80588C7CDE1D5D5D11120094908000D6384508866FB8BF1E37EDDC1E821 + 26BAF6A4C4BFB420D52DF0860066CDD3D0A2D011E23BE322242B4B85699A6754 + 5646DFF03A1E8210E0B6208824A5A5A5EDD8CAFF73D16C7F179E6DE1A9B94C09 + E8A07AFF69402A2130F23370C50D01FBD3CF15A9554B216C81A90A81CFB2E72B + A95B20E1352400082168D3A64D90AD8C1E5714E562516C7FF50D1272B26C8C1D + 66E0CAEB28F12F9D481D050CBD4DDD74CF244D6D254EB7405BD334D9308C8BA2 + D1E8F35E0743F81B1ACE0821A8A8089FAF69CA8B22D9FE56AC94D0E36A13D31E + 64CBFE7572B2E20C913EE400554B806B84B3054A7C17E067F67C5A2412A9F43A + 1EC2BF9000203CA7A4A4640F599679BDFF7D44A9F7BF6EBDE4ECD1C676A68D33 + E413CEA6D57F3AC2DD1B724B0773A7AB1834523C5B20F50920BC868634C273C2 + E1F0003620DE274AE21F9F38D6AC738C3E3D4CEBBE495636D649B4FA4F5752B6 + C01E3A9EFFBB9A480814812DFA04B4ABA9A9F9D2EB78087FE2F9604BF81B6EFB + 6383E1ABEC718028897F09DBDFD171B6728C21BC379BFB37D08D92AE6C690BBC + 61808EA502D902DD3E01CFB24797DADADA3AAF0322FC078D6B84A7B0D5FF10B6 + FA1F29CAEABFA101D07507638791ED2F63D8C216F8E0235AA238902009810806 + 555856ECD465CB6ADFF63A16C27FD0D04678466969E991B22C7F2192ED6FCD5A + 097F3BD7C2FC870C4003D5FBCF0012CA320F882C01AEEE15C0C7022504BAB6C0 + E7344DBB9CBA0512CD0D0900C2135CDBDF7C45512E12C1F6B7B9DE7F858D9913 + 0C1C772625FE6512A9A38039D3550C19AD21169310102321906C818467D0F046 + 7842EBD625E7A9AABE4814DB1F9F20F8D9FFB55799983C93EAFD6724BC909309 + 74ECAA63D1AB2A5AB5146317C0B505FE8B3D9F4AB640A239210140343BE170B8 + C2719CB76559D95B14DBDFAAD592734CBBB8B3E0A1985CBA1725FE6522A95D80 + D953550C1D93DC050806BD8E2A191AD902092FA0318E6876444BFCE3B6BFD56B + 1DE3D6BEA635729C954D5BFF198CBB0B70D9B53A5E7845450B417201527930EC + 7164341AFD3FAFE321FC010D7344B32262E2DF66DBDFB418C26CF50F3264652C + A95D808F5E53707D7F216D81CF6567675FB664C912EA3A41343924008866A3A4 + A424870D728F0A93F8075EEF1F08041C8C199AB4FDD9AB25612C62441391B205 + 5E1FC0438F6A895D0051BEF36050816519E72D5B56FBB2D7B110990F0900A2D9 + 68DDBAE414550DBCD5D060791DCA66F8EA9F77FB9B3F8B6C7F7E21650B8C2E01 + BAF40CE0E3CFC5B1054A12B705C697388E735A4D4DCD52AFE321321B120044B3 + E0DAFE9E9065F942DB1623F18FDBFEDA54D878806C7FBE634B5B204F086C6810 + C6169848088CC7E3FDABABAB277A1D0C91D9D07047340BA150E86FBAAE3FC744 + 009FFD3DDF704DD6FB97705D17B2FDF9169E106801175F19C0CB6F28D84D205B + A0E3383FB2E7D39908A8F23A1E22732101403439AEEDEF1DB6FADF4BA4C4BF63 + 8F8EE3D1193190EDCF9FA476011E7B50C1A0913AEA36C9C80E3A22F47D225B20 + D12CD098473439A2D9FE62319E6CE560D460135D7B59B0D748903D8F8AF004FE + BD17D8B8A17B1073E6AB68C5048108C740640B249A03012E7522930985426DD9 + 60F6A548B6BF95AB259C7B7A1CCF3ECE9480024AFCF33B850E3E784541AF81BA + FDEB32592AC87724012ED5842DD0B6E3CF67656577265B20D1149000209A8CBD + F7DE3B505FBFE94959562E14C5F6B7A95E426EAE8DB1430D5CD18312FF08B8BB + 000E06F45537CD7858D3F2F3254DF13C4B2509D90289A684863EA2C910B1DEFF + 6A36E177BBDCC28CD90DC03A19221CF812029007D4FC1BB8F286003EFD424914 + 071201D716F86FC7714E255B20D1D89000209A84D2D2D2369224BDC556FF7B89 + 52EF7FDD7AC9695361DB33C71BCAF1673151B2962E7F2289CDE67BD9B505DE31 + 4A4FE489640585D087898440D3346F8E46A3F77B1D0C9159D008483409656565 + 372B8A324194C4BF389BEFD76F74CC9ED798E6F8C95636D649428CEE8440B8B6 + C0CBAED1F1E2AB6A62174084E321B75BE04FB22C9F46B640A23111E0F226320D + 36F997B301EB4D3660ED274AE21F2FFAD3BE5D1C8F3E1043E99E64FB23FE972D + FB04F41CA0E3D74A19F9B9640B2432171A0389464724DBDF6FF5FE8131438D64 + BD7FB2FD117F86DB27A047D7201E5DA8A265A138BB00AE2DB06D341AFDCAEB78 + 88CC40804B9BC82444B3FDF110D66D9070C1591616CC36001564FB23B60E9BF4 + 3F7C55C10D03F4F8D24A5916CC16F8424141E1A58B172FA6AB98D8654800108D + C681071EA8AF5BB7E62959562F10C2F697AAF75F6EE3C1FB0D1C7B06D9FE88ED + C0B505F6EFAB6E7A608EA6E5E54A9AA2781D54126E0B8CC7CD73972EADF9BBD7 + B110E90F0D8544A3D1BA75C9B9AA1A7849946E7F7CD5B69EADFEBB5E6E62EA03 + 31B2FD11DB4F1E50FB0B70C575017CFAA558B640C7B1FF23CBF209555555D55E + C743A437240088468127FE398EF33E1BA0F610C5F6B76AB5E4B43B226E3F3D37 + A614EF41897FC4F6F387B6C0ACA4A8F4982D1302EF66BF16A3C8069196D07848 + 34064A381C1E244AE21F87DBFED66D70CC9B7B9AE698FBAC6CDAFA277618D716 + D8F91A1D2FBDA6223F4FAC8440C3C07E2B57467EF63A1E227D11E07226D21D36 + F9EFC79E7E1425F18FB3D9F6373386D2DDD90B9BBC8E88483752B6C08F5F57D0 + E3261DCBAAE4C451800897B8A2282C8EF8D8F6ED23439F7A8A7601889D830400 + B14B74EC08E5934FC2A32449B93D1EF77E1CE22BB44DF54090DBFE8618C96E7F + AB25C882D47627D20CB75B60EFEB0298F78486DC5C47180B6930A8C2308C93AA + AA6ADEF73A16223D11E45226D295F2F2D293745D7FB7BEDE12627B3495F877FE + 991616CC31A8DB1FB1EBE4012B7E052EEE12887FF9B522B76A29842D90895A19 + 4C74BF949393D381BA05123B8300433691AEF06E7F7575754F2B8A729E6D8B91 + F8C7B7FE5B97DB98352986F6A7DB64FB237699D451C0A001EAA64933352D3F4F + 185BA0A3691AEF137061241279D1EB6088F483864662A709854217B2D5FFF36C + 00E2B3BFE79BEC897AFF6CF57FCD9564FB231A996CA0E657E0CAEB45B30526F2 + 6EFEC344F8F195959511AFE321D20B1200C44E515E5E5EC656FD1FB201687791 + 12FF8E6917C7FC0763E0B63F6CF03A222253E01B5C72CBDF6C810D4C5F668B65 + 0B1C1A894446791D0C915E900020768A70383C940D3C2344B1FD25DBB73A1831 + D844B75E166DFD138D0FB705C681CE5D75BCF4BA78B640C6114C047CED753C44 + FA20C0E54BA41BA150E80836E8FC9F48B6BF55AB259C794A1C2F3C194B1E4650 + E21FD1C8A472013E794346F77E0154568B650BB46D6B9124299DAAAAAAEABD8E + 87480F4800103B44DBB6D06A6A42CFCAB27A9E28F5FEEB3649C8CFB531F64E03 + 9777A77AFF4413B2852D70EE7C0D79B90E0449084CD8024DD33CA1B232FAA1D7 + B110E9010D93C40E2162BDFF356B255CD5C9C283731A28F18F687A7281E5BF02 + 97F508E0338112025D5BE0CB2D5AB4B898BA0512DB03090062BB098542AD2549 + 7A4F94C43FD7F6E7B4AEB0ED87261A0AEFF687B57449134D4BEA2860CE3415C3 + C66AA86F901008781D553234D7167841241259E4753084F8D068496C374C00DC + C80698FB4549FCE3B6BF8D758E797D57D31C3FC5CAC63A8956FF44F3A02171AD + FDAD7300AFBDA3A065A118179E9B97F3ABA228C7912D90D8169E0FE2447A5051 + 51116613FF7BB22CEF2DC2EA9F93B0FD1D19C782593114B5612F6CF43A22C22F + A47601664F5531748C90B6C061914864A4D7C11062430280D82E44B2FDFD61BD + FF35923035DA099FC07701ECA42DF0E537D44442A008C9A75B740B3C62E54AB2 + 05127F8E00972B213A6CF2FF0B7BFA4A14DB1F0F61C34609E79E61E189B946F2 + 2A36BD8E8AF01BA2DB02E371EBE57DF689FEEDBDF72046C62E211C240088ADC2 + 6D7FB5B5A1E724493D57946E7F7CEBBFA2CCC6C3536238E634AAF74F78C896B6 + C0051AF272C4B205C662C6B9D5D5357FF73A16424C68D824B68A68B63F5E9295 + FBFEBB743231F541AAF7BF4DA42D1E7C62E245921CF739D5BDC1761F92FBEC6C + F11A7DB6DB261758B114E87CAD58B64077C78E458613229148A5D7F110E24102 + 80F85378E21F5BF57FC20692D6226CFDF355FEAAD592D3F6B0B8FDC2FC98D2AA + 0294F8B725FC6EE693BA8AE4F9347FE6B59AF8C68D25A17E3DF8B9706285CA93 + D61A1A92B77F30E824F229F8060F7FD675F657F13F9BEB24FF4EF667137F47E2 + EF619A200EDA71D982FFB205DEADA3BE1EC2D8027942A0699A3746A3D1C95E07 + 438807DDC6C49FA184C3E1C1A224FE71F804B56123AC7ED71BC6D8F156366DFD + BBF0C93AC81E32FB9AD8C4BE619584152BD984DF009B0BA60D7550366E90F08F + 1F64AC5C93CC56AF5D2EA1A636F9E1959638282976128995A5450E5AB500F2D9 + 2A76CFDD1DB428603A20C7412E1303B939EC9F69E924C5454C4AE65D88B131E4 + 3D5AF2E9BC0E01BCF59E82962DBC17CC1CBE0B60DBF6124DD34E5CB66C59D4EB + 7808B1A0E193F8438A8A8AF66283C6125112FF38EBD80AF698236DCC7F288662 + BEFAF769C5F3C48A93AFF4D9440E9DFD623D9BCCA3127EAD047E5D26E3932F64 + BCFFB18C7FFD8258DD46AE0AE4202427B1B2E7AB7F9BBDA22AD87C56CD8595C5 + 1EDC45C19F6DF688278E002464E5D838787F1BFBECE560AFDD6D1C79B883430E + B49197E760B756ECF7B0671852427838B67F770652BB000B662BB87DB88EF51B + 65E4640B911048B640E24FF1E9ED4A6C8D934E82BA644968AC2CABB78892F847 + B63F7792E1ABFD6C24CEE6D754034B7E91F0E537329E7D496193BE02A3414630 + CB41167B682AB69A90969AACFF6C92925C4160B009DE64AB7DFE30D8CABFA2C2 + C609EDE338EA88384E3DD166C280FD7BBBB97F68938F85809B10D8BD4B100B9E + 51515820962D90F1172602BEF13A1E421C04B83C09D1A8A8081DCF56FF1F8892 + F8B7D9F677BA85271EF19FED6FF38A3F178995F6F208F0E3CF3266CE51F1C473 + 0A9CB884FC82643B643EE1277E7F23DFD92991C0FFDE9891CC1FA863DF49386C + A3C30516CE3F338E63DAD9C82D46F25820E6F5A7E611850E3E7E5DC1B537EA56 + 65B5AC14E43B9200BB00095BA0E3587FDF6BAFE885640B2452900020FE8BB66D + DB6AD168749124C9673A7C29E73129DB5F399B68E64E8DE1689FD9FE1293393F + 5F668F86B5C0A257154C9BADE0D32F79FB5709AD5A3890E55DFE67762A2EFEEF + 7231B03AF17D38E8707E1C37DE60A1FD31ECBAC961BFA18EAD3C2D9FED062476 + 011CF4EFA36E7A689EAEE76443255B20212A7EBA3589ED201C0E5FC056FF2F98 + A6294CE2DF46369174BDDCC4D407FC65FB4B4CFEFC9C5F75F0CD270A464F50F1 + E22B0A5BCDB1157F9E387E730EB767D62C97505AECA0E73526AEBC348E3DF777 + 125642A7C16722201758B90CE8744D009F7F25B3EFCAEB8092B8098195ECFE3E + 9A1202098E9F6E4B621BB46EDD3AC426FECF6459AE1025F16FFD060947B58D63 + E1C331F8C9F6C72754D99D385E7E5EC66D77E9F87EB18CD2526F56FCDB039FE4 + B9CD70C50A1907EC1FC77D230D9C73617217C9DE0061E36E6C36DB02A7ABB863 + 94B07D02C62069EC247C0C090062336CF53F8C0D10C345B1FDA506CE11830C74 + EB65F966EB3F3181E42071963EF30105C3C6EA092154B49B931006A2C3BFA395 + AB92BB14FCBBBBE1FA78C2AAE8D4F96827C0ED16D8E96A1DAFBCA9242C9422BC + F754422013F9FB5455552DF13A1EC25B04B82409116093FFE1ECE96B916C7FFC + 6CF9D413E378E9D95872DBDF07897F89C99F7BFA830EC68FD170CB100D852D24 + 512C65DB0D9FEC78C5C6B56B1CDC37CAC480C1ECCB6B907C731C90DA05F8F44D + 19D7F60B6059B59C1044227C874CE48389FCA191486494D7B110DEE2835B91D8 + 166DDAB409C6E3E6B392A49C2D8AED8F4F1EF97936C6DD65A053B7B86F56FF89 + 72BD6CB25FF8988A7EB7EB896C7B5E884784896347E1DF17CFDFE0EE84C9771B + E8D8C54A26069AFE1001295B60B72B8358F8BC9A28112CC2FB4E897CD3C45F56 + AC205BA09F11E07224BCA675EBF0B16C55F09148B63F9EF9DFF9620BB3E7B125 + E35A7F1C1EA7568D5F7F20A3CB0D01FCFC8B8CE2564EA2704FBAC26B352C5F25 + 61BFBD6D3C30C140FB53EC641E471ABFA71DA210896E81D7F4D5ADAA882C3311 + 208B20E692B6C0F82BEDDB47CE7FEA29CA05F02B24007C4EEBD6AD5B5896B540 + 30DB9F5D5166DBB3271B6AFBD3594C6BBD8EAAE9499DFB5BEB1DF4BC45C7AC47 + 749497DB6971E6BF2D78F25F559584F3CF66826EB281A23DD8FB5DEFA75D0007 + 37F75137CD7A54D773B2C8164888831F6E41622B8442A11B755DBF5F24DBDFA6 + 7A58D776318C0953AC6CAC93FCB15A74278A87A7AAE871B38EE2DD322B6B9E0B + 99D56B8007EF3770F50D167CF3BD727281559540C76E017C219E2DB08ADDFFED + 962E5D5AE3753C44F3E3F9804F78479B366D4AD9C4FF311B08F61025F16FFD06 + E0A8236C2C9CE32FDB1FAFEBBFAA1AB8F90E1D8F3FAD2254929EE7FE7F065FED + 476B255CD1C1C2C4D1065A95C137BD1CD2C0167867241219E1753044F34302C0 + C784C3E13BD900709708B6BFFFAAF73FD440D79EFEB1FD252874F0F622C5EEDE + 5777366C941441DAC9362A3136F1E5E53A78788A8193CFF7516227C7ED16D8E9 + 2A1DAFBC259E2DD034A5C357ACA8FED6EB7888E645804B90F082A2A2F0E19A26 + 8EED8F87B0B10E38FBB4389E9C67245FF481ED6F334C003C344535AEEBA7DB65 + 65080AF095343A3C21B0AA5AC6FD6363B8F176D357C700A95D80CFDE927175AF + 00AAA372C21520C2F7CC13026D3BFE6A5656F6DF962C59E2D72E0EBE8404800F + E9D811CAC71F875F9665E54C516C7FBCD04D4599CD5687311C7DAABFEAFDF322 + 39901D8CB95B4B6C11874302CC0A4DC48A9512BA5D61E29E3B4D1496B0171ABC + 8EA819716D81BD7B04F0F8D35AC21E294A9E074F088CC71BDA2F5DBAFC53AF63 + 219A0FBF0CB1C416B46E5D72AEA2045E8AC5C4B1FD7101D0E1420B731F6BF055 + BDFF0441606D2D70DB701D73E6AB286A95B96F9EDB3B4F38D6C2A43126F63DCC + F14F8E478ADC641BE7B33B06ACEFBE57E4962DC4B0054A92CCEE43FB8D482472 + 36A844B06F2001E03378E29F61185FCAB25C26C2D67FCAF6571EB6ED395363EA + 31A739BEB0FDFD1739C07F7E90D07F8886D7DF4EF691CF547881A7BDF7B031FD + 3E03479DC2E699B5FE1A82524701B7DEAC6E9A315BA86E818EA66992699A1730 + 11B0C8EB6088E6C15F771FA19496960ED1755D88C43F8EC5E6807A3FDAFEB684 + 0980653F4B183054C3CBAFA9685198B91F405D9D84FDF78D63DABD26DA9E184F + E601F88D20B02A2AAC2DB03A1E8FB75DBE7C79ADD7F1104D8F0FEF3EFF525454 + B41753F94B4449FCE370DB5FBB236C3C3D378696E5F0DF96308709804A2600FA + FB41006C92B0EF5E4901D0EEAFFE1400A95D80B9D3550C16D0166818C65D3535 + 35C3BD0E86687AFC77F7F918DEED4F5194E1A224FEF9DAF6B7253E12001B364A + 38E2B038A6DC6DE0906378F287D71179846B0BBCF42A1DAFBDAD20279B6C8144 + F323C02547340765656587B21BFB5B5156FF3C84BA4DC059A7FAD4F6B7253E12 + 006BD6264B028F1F61A07C6FF6C226AF23F286D42EC0E76FCBB8EA86002235E2 + D8026539D127E0B58282C20B162F5E6C781D0FD1749000F0074A28147A4355D5 + 934559FDF36CF0B2908D4767C4D0EE149FD9FE7E8F8F04C02AF63D5FFA370BF7 + 0E3750D21ABEA906F887A46C81D70530EF490D39590E04490874FB0434B4AFAE + 265B6026E3D721D75784C3E10B344D7B41AC7AFF12BA743631F58198FF6C7FBF + C74702A07685849B6F30317A88093D97BDE0F7F525FBEED744808BBB04F0E5D7 + 222504266D816CD1D069D9B2656BBC8E87681A3C9F0C88A6A5B8B8B8445194AF + 64590E8BB0F5CF4924FEFDC5C633F3626811662FD4791D91C7F84800D42C9770 + FB8D26460E3621F372C77E3DF671D9B24FC0B0B1FAE6BC180148D8020DC3B831 + 1A8D4EF63A18A269200190E18442A1BBD88D7CA728B6BF54C6F3C8C13E4FFCDB + 121F09804844C2C4B1066E1AC866FE3AF6C56740BBE35D862704CAC0B91705F0 + EE87320A0BBC0E28899B2FB4948D1D47932D3033F1FBD09BD11415951DA669CE + 37A224FE717812D849C7C6F1CAA258B2DE98CF578009980058FA63B210D02B6F + 66BE00983CCE40DF5BD817BF81040027B50BF0F0341543468B670B344D737834 + 1ABDCBEB6088C68704408672E08107EAEBD6AD7D499294D36D5B8CC43FEE01CF + CFB3317EA4810E5D7CD60D6E6BB0C1BE761930F04E1D0B9F57D1AA85F7237F53 + C10480356FA6215D75BDA9F8AD0AE05649F483002EBD524C5BA06549872D5F5E + FD9DD7F1108D8B009718D1149495151F1308043F696810A7DE3FF78077B8C0C2 + 1C5EEF7FAD205D50444007CC3AB0D59F86F1D334941467A600B0D96A7FED5AA7 + E1B1074CF9922E964E02E03752BB005FBC23E3CAEB85B405BE515D1D3913FE4E + D7CD38E80ECC40CACBCB5BC6E3F185ECC63DD571BCDF634DD9FEC2A5361E9D19 + C351A7F8B0DEFFD608000DEB813B466A98F4201300459939C61A067BAB0107B3 + 261938EB12FFF501D8265BD8021F7D524356960355105B6020A0B0EFCF3CB7BA + BAE6EF5EC742341E74076620A150A89FAEEB9344B1FD25EBFD93EDEF4FF18900 + E067DB2D0A1C3C78BF8153CE2701F087E4B08F250A5C745500FFF7B58C3C616C + 81893E01514551DA555555557B1D0FD138D01D986170DB9FAAAA9FB31BB6B528 + 897F29DBDFB38FC6501802D9FE7E0F13009BD60283990098364B4371860A808D + 75120EDC2F8E69E34C1CE1D74640DB60CB3E0143C5B205828D2B608B8ABED168 + 74AAD7B1108D03DD8119065BFD0FD7346D9865797FF6BF65BDFFB1430D5C4DB6 + BF3F46036CB63A1E3A46C3DD93349466680E00CF01697B78B20FC0C147FBB80F + C0B6E0B6400538F3BC003EF84411C615E226042E8BC7E3ED6A6B6B977B1D0FB1 + EBD0509C418866FBDB5CEFFF94389E7CCC485ABEBCD725E2C1F321731D4CBA57 + C34DB7EB0887BDFFEE9A82441F80B3AC840BC4CF7D00B6456A17E0E9790A060C + D5B17E838C9C6C311202DD5D8011D168F44EAF6321761D12009983545E1E7A83 + DDA2A78A62FB5BBF414279D8C6BCE9311C79B2CFEBFD6F0D9EE8157070DF380D + 038766AE0058B55A42A78B2C8CA33E00DB47A18DABAF08E2D9452AF2984014E1 + DE215B606621C025453406656525E7EABAFE522CE6FDE4CF49D9FE2E3ACFC2BC + F90D94F8B735F80E409E8329F769E8776BE60A80DAE51206F43631EA0E135A0E + A80FC0B628043E7F5BE2DD02CD488DAC14E43BB208BB00DC1608986F5555D59C + E6752CC4AE41022003282929295614E53BA6CE4B44D8FA776D7F76B8D48E3F36 + 33A6B523DBDFB6297430EF010D57DF90B90260F90A09BDAF353166A8896C36B9 + 21E675448293B0053AE8DF47DD34FB313D100C405155AF834A92B4051AE75557 + D7BEEC752CC4CE430220FDE1AD7E878992F8C7E16134C410EF7E95119B30C5CA + 4E647B67E69CD67814F286305AFC9ADEBAC3048020C37CE352532BE136B71190 + 9205DA01D81E7298988E02175E19C057DFCAC8CBF53AA024EE51408D6DDB87D6 + D4D4ACF03A1E62E7200190E6949696B661ABFF5FF9CF22ACFE391B36026D0FB7 + F1FC63311494826C7FDB031300CF3CA21A57F5D4EC820229286760A144DE0760 + FA04033D6F3641A270FB4825043E3243C5A09142F509D832217004929D3D8834 + 8304409AC36D7FEC461C168F7B7FFFFD97ED6F9881AB6F20DBDF76C304C0EBCF + 29B8F646EEFD9610D0BD0EA8F1E10260CE74DE05D2A422403B42AA4FC0153A5E + 7F5749080011EE29C90DC2B6ED364C042CF33A1E62C711E032227696E2E2E243 + D8E4FF9D48B63F2E00CE38398E858F1BC9358118A712E2C304C0CB0B1574EFA7 + B3015582A6791D50E3C2FB00AC63ABFE87A7C6D0F91A8B04C00E90DA05F8F25D + 992704A23A2A233F4F0C5BA0A228B02C8BBA05A6297417A62F52381C7E5F96E5 + E36D5B9C7AFFA1121B0B66C5D0F624B2FDED104C00BCBD48C17537EB09BFBC28 + D5DF1A0BDE07400F38984D7D00760EB74FC0559D8378E11515B939E2D802F959 + 8E69922D301D11E01222760636F95FA069DA0BA6697A1D4A0237F10F5775B2A8 + DEFFCE90EFE09B8F14F4B94DC33F162B89013E9348F4016022E7C189D40760A7 + 2904BE7847C215D705CC68AD68B640EBEDAAAAE8A95EC742EC187417A621A5A5 + A5456CE5FF0F516C7F9C44E2DF61365E981F437E0928F16F472970F0F93B0A7A + 0FD4F1AF5F9295DF3209DE07E09003E3987A8F89C38F6302603D0D3D3BCC16B6 + C0871FD703019D6C81C4AE4177611A120A8546B2D5FF10516C7FA9CCE45183A9 + DEFF4E930FFCF33309FD6ED7F1E5374AA2F25B2651B749C23E7BDA9876AF81A3 + 4EA646403B4D0ED34E35C0055708690B5C6EDBF6C1640B4C1FE82E4C338A8BCB + 0ED534E75BFED589B2FA5FB71E38A1BD8D57163125C04F24C4D025E9453650FD + 0B70CB501DCFBFACA2650B31BEDBC68257853CF2F038265323A05D62B32D70A6 + 8A412384B4058E8A46A343BD8E85D83E4800A411279D04F5DFFF0EBDCE6EB593 + 45A9F79FB2FD8D19CAED5D94DDBDD3B095DDAF3F4AE83F44C36B6FA9282C1060 + 446F447862E3796E23A00A6A04B46BF06D7F05B8F472116D819410984E0870D9 + 10DB4B7979693B4DD33F8FC5C4586293EDAF116183F8F24A60E09D3A9E7C5645 + AB96992500128D802EB670EF7003C515A04640BB406A17E0FFDE93D1A97B00B5 + CB6514E48B610B741302DF69DF3E7AFA534F517120D121019026949797B7B46D + FB7949924F701C716C7FA5C5369E9C1DC31164FBDB3574C0AC03EE18AD61C234 + 0D25C5028CE68D48ED0A09375D6F62F41013813C5019E05DC5B505F6BE2E8027 + 9860D454EEC9F73AA82481800ACB32DA5556D67CE9752CC4D6A1E13A4D088542 + 7D755D9F6C9A269F193CFFDE52B6BF2E9D2C4C21DBDFAE130036AD05068FD030 + ED610DC5BB65D687C9FB000CBA39D90740E2550EC570AFA6373940DD0AE0C4F3 + 02C60F3F296ACB1662D802D922056C91F251241239DEEB5888ADE3F944426C1B + 6EFB9324E96B5996CB4449FCE3B6BF230EB3B168410C7945A033DD5D4563FA89 + AD8A878ED13076A286D21231BEE7C68297019E30C6C0CD03D9CCBF890D3BDE6F + 62A53DA9A3805B6F54EB1F7844D745EA16A8691A4F08BC90898017BD8E85F873 + 4800A4016CF53F8ADD50778866FB1B7D87812E54EFBF71E0CD7FB21D4CBC5743 + FFC199D712980B80C9E30CF4E502603D09804623C03ECE95C0059707F0D577E2 + D902D9E3A06834BAD2EB78883F86866DC149DAFEF02DFF5994D5FFDA7540FB76 + 36DE7CAD0130244AFC6B0CB800C87730950980BEB766A400B0E6CE30A4AB6F30 + 15728A341EA95D80793355DC365C47CC10CE16389A0980215EC742FC3174270A + 4E5959E85D49524E12A5DE7F5D9D84FC7C1B93C71AB8F0722AE9DAA8143A983B + 5D43B7DE3ACA980010600C6F14F8A5BB7EBD137B64862977E8626974CD34325B + D802DF7857419650B640705BE0A1CB9757FDC3EB7888FF4580CB84F833C2E1F0 + 798180B2281613C34D633B4901F0B7732DCC9BCF56FF6B33B069BD973001F0C4 + C32ABAF7D599C8E2962AAF036A1C128D807407B3261938BB0389C6C626B50BF0 + D5FB323A760B60F90A916C813C2130FEDEB1C7464F255BA078D09D2828AD5BB7 + 0E5996F52D53D145226CFDBBB63FBBB4386E3D31DBD0DB9EC4625AEB75541906 + 1300AF3DABA0C74D3A36D54B08E85E07D438F09C115ED9F0C109064EA646404D + 836B0BEC737D008F2D5413D78E280981640B1417BA130585ADFE7BABAA3A5594 + C43F1E46CC40BCFB1586317EAA9595A8E5EEBD2EC92C980078E94905DD6FD4E1 + D81234CDEB801A07DE08E8C07DE3983ACE44DB93A80F409391CD3EEB15C0B99D + 03F85AA884C0A42D5096E50BAAAAAA567B1D0FF11B74270A482814DA8D3D7DCB + 6E98B008AB7F4EC2F677A88D979E8821976C7F4D031300EF2C52707D7F1DABD6 + 488912CB99C0FA0D128E3A22D907E0A076EC7ADEE0754499C9967D02868CD637 + 97E916016E0B340CA36F341A9DEA752CC46F90001090D2D2D2D1BAAE0F1661F5 + 9FAAF7CFB714C7DD65E0AAEBC9F6D76414F0F2AE0AFADCAA61F1CF0A7273C410 + 7FBB4AA20FC0996E1F807D41ADA29B12BEEDAF3B38F58C203EFB3F9E0BE07540 + 497842A06DDB11F67C68241259E5753C44121AC6054334DB1F0FA1BE0138FDAF + 712C9C6F242D7FDEEB92CC240FF8FE8B644BE0CFBF52909FE7FDF7DF18F03E00 + 9DDD3E0045D407A0E92974F0E20226246F0B60C306093939622404725BA06118 + 636A6A6AEEF03A1622090900C1282F2F7D9FDD2A278862FBE3F5FE8B77B3F1CC + 23311C7E02D5FB6F52784BE07F03B70CCBAC96C0BC0FC08DD79B18437D009A07 + 3721F0AACE41BCF8AA829C6CB205127F8C00970591221C0E9F1F08282F8A62FB + 8BB3302CF6B8EC62AAF7DF2CF096C03FB82D81DFCE9C96C0A93E00A3EE301325 + 8FA90F403350007CF581848E5D8358BE5212CC16687D505D5D73A2D7B1102400 + 84814DFEAD1CC7F989DD20AD44D8FAE7627DD56AC9DE7BCFB8F5C55B313DD802 + 94F8D7D464012B784BE0BB743CF14CE6B404E66580C78F36D0FF56EA03D06CB8 + BB007DAF0FE051E16C810AD822E7824824B2C8EB58FC0E090041282D2D1DA3EB + FA201112FF38467295161FD8C7306E1D6665D1D67F33C006E9D80660F0480D93 + 1ED4505294390280FA0078403650B71238E7D200BEF9878C5C616C81893E012B + 4DD33C64C58A15355EC7E36768481700D112FF38EBD603C71F6DE3D5371A808D + EC3211E35422B3713B02DE315AC33DF7674E47C04804E6DC99A674F5F5A64A45 + 809A8F2DFB04DC21982D902704B2C54E9F482432CDEB58FC0CDD8D1E73E79D90 + 67CF2E7D8FDD12C78B92F897B0FDB181E29E61C96E7F34683713896D5B073326 + 6AE8D53F331A02F13C928D1B9DD8DCE9D407C0135C5BE0C9A707F1C557C2D902 + A3ECF910B2057A07DD8D1ED3BA75A8ADA2685FC662626CFDF37AFF0D64FBF38E + 425EC84543D79E992100527D001E9A64E01CEA03E00DEC9A5AF4A482DE0385B4 + 058EADA9A919EC752C7E85EE468F0985429FC9B27294E388B1FAE7B6BFA2DD1C + 3C37AF01871D4FB6BF66870DD673A66BD635BD35271C46DA1703E662B2552B07 + 0F8CA73E009EE126045ED9398845AFAAC8CE7284683445B640EFA1BBD143D8E4 + 7FA1AEEBCF9BA618BE28B7DE3FBA74B2307926D9FE3C81098067E6A9E6D5BD34 + 3B37570A288AD701ED1ABC0FC0016E1F8023A90F807714005F7F20A14357DD58 + B14A51F3F31C59845D0039A144AC8FAAAA6A8EF73A163F4277A347B8B6BF9FD9 + 0DD05294C4BF8D75C0E107DB78E5A918B25B816C7F5EC004C0DF9F4A7604340C + 097A9A7704E47D008E6E1BC7A4B1D407C053DCFC92017DD4FA87E7EB015D834C + B640820480479496968E65ABFFDB45B1FDF196ADD959C0982154EFDF537843A0 + 9714DCD05FC7CA55128241AF03DA35781F8073137D004CB4DED7A13E005E92CD + 34FD2AE0AC8E017CFB4F19B9395E079482DB02EDD58AA2EC43DD029B171AE23D + 4054DB5FBBBFD878E7AD06202651E29F57B055DA976E43A01F32A02110EF0370 + D92516C6F13E00E5A03E001E92B2053EFAA08A5BEFD413C77D5CF48B3004B909 + 8177BB7D02BC4F88F20924003CA0BCBCF42376C91F2B8AED8F9FD3F2C63333C6 + C7706E474AD4F214B721D08D83747CF67FE9DF1088F701E8779D89B143A90F80 + 10A8C947C7CB74BCC584662008C802DCEEA98440260242541CA8F910E0ABF717 + 1515E10B54557941947AFF5C83D4D74B38FF2C0B8F3ED140897F5E93032CFB59 + C280A11A5E7E4D458BC2F4FE32781F80C1FD4D8C1C4C7D0084815D53DF7E28A3 + 43B7006A97CB099129C22E80A228304D936C81CD08098066A4A8A8A85455D5EF + 25496E29C22CCB45F7FA0D925DD42A6E3E33CF081C7E1C8B699DD751F91CDE0F + A00A892DDA0519D00F809701BE6F948101B7511F0061706D81575C1AC4CB6FA8 + C80A922DD0AF90006846C2E1706F2600A68A92F8C7C3304CD8DDAF3062F74DB1 + B21216ADF49E6FD21FB71FC0A0911A2667403F002E00A68C33D087F701A0EB4B + 1C0A806F3E9470F1D57A6CE52A4513C9162849D627959535C77A1D8B1F2001D0 + 4C949797B7B46DFB07A6728B4549FCE3B6BFC30EB6F1DAD33164F16E7F94A0E5 + 3D7C9B9C09B3A1A3358C9990DEFD00F882AEBA5AC2ECA906AEE96D526E8948B8 + B6C05BFAAAF5B31F17CB16A8EB0A0C237E61241279D1EB58321DBA239B89D2D2 + D27B745DBF5584D5FFE67AFF6CB579EF080357F620DB9F30F0ADD86C07E3EFD1 + 70CB90F42E071CB799C8DC00CC9A6CA0F335D4534238B298E65F039C794900DF + 7E2F9C2D708DA6697B2D5BB66C8DD7D1643274473603C5C5E587689AF31DFF59 + 84D53F4FFCE316A0D34E8A63E182186092ED4F18DC95D9D47B35F4BD35BD0500 + EF03A0E90E66DD6FE01C72970847CA16F8D8832A6E19A6275A808B620B741302 + EFA9A9A9B9DDEB583219BA239B818A8AD0278EA31C238AED8FD7FBE7C9658BE6 + 37E090636D1A9845A3D0C1DC999AD5AD677AF703E07D00766BE560E60403279F + 47024048F8B63FBBC23A76D6F1D6FB4A6257901202FD03DD914D0CAFF71F08A8 + CF1B8618B63FDE9E956FCDF2E22C936750BD7F21E1FD001E55CDAEBD34272747 + D2D3B51F00AF2F71F001714CB9C7C41127501F006161D7DB771FCBB8E0B22056 + AD9150902F862D9012029B1EBA239B90D6AD5BB7304DF31749920B459865B9A8 + 5EB356B2F7DA3D6E7EF94E2CA0F3DEE094F8271E6E3F806B6FD261A6713F80BA + 4D12F6DEC3C6B47B0D1C7D2AED00088B6B0BEC7743000B9E55A1C87C0BDEEBA0 + 92B87D028E8844225F7B1D4B2642776413C256FFE354551D188F8BB1FA4FD9FE + 7A74318C71F75B411A9005C5ED07D073808E152BD3B71F40A211D091714C1E6B + E0C023A91190D064B1B1613DD0F6E440EC975F15AD45A118B6407E1460DBF6D7 + D168F408AF63C94468066822444BFCE3FC97ED8FAFFEA92CAB98F07E00EF26FB + 01FCF82F053969DA0F8037023AE70C0B13465123A0B48009CF5B6F521B1E9AA7 + EB22D902354DE325822F6222E079AF63C934480034111515A14F1D47395A94C4 + 3F6EFB633735C68F8CE18A1E71B2FD894C1EB0F8CB643F804FBF4CDF7E00BC11 + D0E51D2C8CBBCBC06ED408487C74F615AD07CEB82480EF04B205BABB006B9910 + D8936C818D0B4D014D40381CFE4B20A07C2552BD7FB2FDA5116E3F80FE4334FC + FDF5F4ED07906804D4C3C4986126827CC729E67544C4D648D9021F7F48C180A1 + 01E16C819665DD1B8D466FF53A964C82044013100A85BE9165F93011B6FE53B6 + BF96ECC67EF989061CDC9E6C7FC2C306DD95D5C97E00F39F4EDF7E00BC11D0ED + 3799183D841A01A50D095BA0838E9D03640BF401341334326CF2BF48D7F5674D + 538CD1CE4DFC4397CE16264D27DB5F5A10001AD60183793F8087D2B71F00EF03 + 307D82819E37531F80B4A2D0C13F3E91715EA72056AF15CD1618FFACB2327A8C + D7B1640A24001A11D7F6F71F76A11688B0FAE7F0C4BF430FB2F1E67331040A40 + E7B0E980BB5ABE639486BBEF4FDF7E005C00CC9966A06B2FEA039056B8D5286F + ECA563DE136A227748948440B74F00D9021B09BA2B1B11B6FABF5755D55B44B1 + FD35C4926778770F7313FFD8204C5F781AE00EC033266AE8D53F7DCB01472230 + 664F35E56B7A9B2A098034838D1BB175C06917099910F8B5A228A7555555ADF6 + 3A9E7487EECA464244DBDFFA0DC05F0EB1F1FE7B0D4C0D50E25F5A51E860F654 + 0DD7F6D5515626C616EC8EC03570DD26C7983BCD942EB9CAD24800A42185C984 + C0DB860750CF869060C0EB8092B045164F08EC138944A6791D4BBA4377652351 + 5111FADC719476A2D8FE7819D6BC5C36894C6EC0191751E25FDAC106DF27E7A8 + B8B69F8E9C1C712AB36D2F3CEF44531D3C74BF81732FA52A806909DFF60F3A38 + E1A420BEF9878CFC3CAF034AC27701D8226BA5A669FB2C5DBA74ADD7F1A43374 + 573602E5E5A18B982A7D56947AFF5C833434248BB03CFE640325FEA5234C00BC + BC50410F5E0ED89212E7B0E9045F3116EFE660E678037FA54640E90BBB0E5F7F + 4E46F77E416CD8282137478CDD28D716785F341A1DE8752CE90CDD95BB487171 + 71099BFC7F661F65BE08B32C5FFDAFDF20D9AD5AC68D45F38DE0C147B398D67B + 1D15B1C3B081F7DD9714DC3040C7F29512B2D2AC1C705D9D84FDF68963EA3813 + EDFE4A8D80D216B74FC0159706F1F73795C43100D9023307BA2B77917038DC9B + 0980A99625C601BB6925AC7FF6355719C67DBCDE3FD9AFD29302075FB8E5807F + 4AC372C0BC0FC031EDE2983486FA00A43D6C69F3CFCF249CD32988B56C3CE195 + 2945D80590134A24FE655555B49DD7B1A42B24007681366DDA141A86B1845D88 + AD4449FCABAB030E39C8C65BCFC740DDFED2985439E0C13A3EFD22FDCA01A7FA + 008C1F69A2CD7ED40720AD715D2937F5D2F18880B6C058CCA23E013B0909805D + 20140A8D67ABFFFE22D8FE52F5FE357673DE3F3A86CBBAD3B96B5A93032CFD49 + C280A1E9590E78E56A095774B070EF7003ADCA404234DD71BB059E726100FF5C + 2C271253C5808F71CE7A3606EF535B5BBBDCEB68D20D9A217612D16C7F3CF1CF + 30805379BDFF2762EC1764FB4B6BD880BBAA1A1878A78EC79F56B15B9A9503E6 + 7D00FA5E6B622CEF03C00B50511F80F48789D0F9B314DC7A9758B6409E10C804 + 00D902770212003B497979E84B76E9B515C5F6B77EBD84820207AF3DDD80038F + 26DB5FDAE396031E3442C39459E9570E98F701B8638089117798EC3601F501C8 + 045C5BE0F12706F1ED3FC5B205B27178755656D69EBFFCF2CB3AAFE349276896 + D809CACA4A2FD634ED19516C7FFC04826F425CD6C1C2FDD30CAABB9E09B8E580 + 878CD6307662FA9503E66580C78D3430F076F626EAD9F528C6AD42EC2A850EDE + 7C4146B7DE416CA813CE1638211A8D0EF03A96748204C00EB2E79E7B16343434 + 54B28F2E4F845996AFFED7AC95ECD61571E39F1FC5820A3F9BA3F3D6F427550E + 7882865E03D2AF1C30170053C619E833901A016514AE2DF0F24B8378E54D15C1 + 8043B6C0348604C00E2252E21F87DBFE4C13F6F55D0DE3EE095690B6FE3308B6 + DA9A3B5D33BBF5D69C7018BAD7E1EC08BC0FC09C69A6D4B597496580330DB6F4 + F9FE0B09675FAA37AC5DA7E8F9798E2CC22E40D216687D555555D3D6EB58D205 + BA33778056AD5AE50583C144591D1112FF38759B80830FB0F1F60B31E87CF56F + 781D11D1683001F0F423AA754D5FCDC9CE96B4742907CCB5F1A64D8EF9F01453 + EA70B5458D80320D77776AE04D6AC3EC47755D55216B02D9024DD3BCA4BABAE6 + 59AF634907E8CEDC015AB72E7DCEB6D5BF8992F897B0FDB11B6FD298183A93ED + 2FF36002E0A527155C77737A95034EF5017870A281F33AD17599916401E606E0 + E40B02F8E70F3272B2BD0E2845C216B8912DD4CA292170DBD09DB99D84C3E1C3 + 0301F5EB584C0C6F5DC2F6C706DA534F746D7F314AB4CA387839E09715F41CA0 + 276C75E9520E985BC4B86B6106EF03702E09808C855D9F0B662BB86970207114 + C95B8F8BB031EA26044E8C46A3FDBD8E4574E8CEDC4E9800582C49D201226CFD + BBF5FE13D5E1DE78B601071C45B6BF8C8497037E879703D6F1D3BFF92ACBFB6B + 6F7BA8DB2461BFBD6C4C1D67A0DDC9D4072063E14752010797760AE0AD0F94C4 + 0E95480981ECB9555555D56AAFE31119BA33B783B2B2B20E4C553E2554BD7FB6 + FAEF7AB9850953C9F697B1B835D8FB0DD2F1C557E9530E988BD3F6BC0FC05803 + 07B4A53E00194DA183C59FC938B77310ABD7245B900BB046A25D80ED8404C036 + 706D7F554C4DE68AB0FAE7A412FFDE5D1483CA13FF1ABC8E88681282C0DA5AE0 + D6BB343CB240C36EADC4B8FEB605EF0370F6E94C9C8EA23E00190F5FF1E7DBB8 + F4E220DE78573C5B20130107D7D6D67EEF753CA24202601B949696DEAF69DA8D + A2D8FE1A62489C05DF379C12FF321EB71AE0ED23344C9BA5A1384DAA01F23E00 + 5776B4308EF7010883EA52643A6C1112FD45C27167EBF52B562A81827C716C81 + 6CDCFE291A8DEEEF752CA242B3C756F8ADDEBF18DB5A9C0D1B81430EB4F1D187 + 0DC90A6B629C4A104D01CFFA8F3B18365AC7E8F1E9530D70F90A09BDAF357137 + F501F00F850E060D506333E7E86CBD248E2D5055552E023A5657573FED752C22 + 4202602B5454947EED38EAE1A2D8FE36BAA5371F7BA0017F3D9712FF321E9E64 + 95E5E0DEB11A6E1D963ED5007FEB0360B0651889545FA003E626E0AFE707F0FD + 8FE2D802F95180E33875D9D9D9E1254B96ACF73A1ED1A019E44F282B2BBD8449 + D9A745A9F7CF35482C26E1ECD32C3CBE90ADFED7C994F897E9B8055726DDA3E1 + A6DB75764D8AB313B535127D004618183888FA00F88A42074F3CACA0DFA0002C + C16C81A6694EAAA9A9B9C9EB58448304C01F505A5A5A24CBF27FD8C79323C22C + EBDAFEECC28278ECB5A78D2CCAACF6116C507DEA1115D7F6D39195C50733AF03 + DA36893E00F71AE8730BF501F015295B60E700DE7E5F8126982D90FA04FC2F24 + 00FE807038DC9BA9C6A9A224FE256C7F169C1E5D0CE39E89568006551FE15603 + EC71B3CEAE01F1AB01F2B1B6BA5AC283930CF4E867D23195DF60D7EB8F5F4838 + F56F59D8B03159AB44845D009E102849D6379595357FF13A1691A0BBF377ECBD + F7DEF9757575CBD805532092EDEFA0FD6DBCFF520C0AD9FEFC855B0DB0D72D3A + 6A968B5F0D906BE6FA7AE0C1FB0D74EA669100F01BEEB155FF3E3A1E7F4A4DFC + 5A1564D72AD927C03EBCBABAFA5BAF631105BA3B7F476969E9244DD3FA89B0FA + 4FD5FB57D97D34E5EE181B50C9F6E73BD860FAB95B0DF0E734A806C8CB53ABAA + 8387A80F807FE122952D52763F3C90B005B62814C316C88F026CDBFE391A8DEE + E7752CA24077E7161417971DAA69F85614DB1F4FFC33D9807A0AD5FBF72F79C0 + 0FFF27E1A6C13A3EFE5CFC6A80BC0F4069B18319F7193889FA00F8974207D3EF + 5362C3EF0DE86C1C9374419A59BBB6C04BABABAB9FF23A1611A0BB730B2A2A4A + BF751CF550516C7FBCA42AB7FDBDF3623DF63BD2A1C1D48FE4004B7F94D07F88 + 8657DE54C156535E47B455781F807DDD3E00479D4202C0B7F06D7F26564F3A21 + 886FFE21B371CCEB8092B8B6C04DB158AC74D5AA55BE4FA5A6BBD3A5ACACACA3 + AACA0B4D538C25B6E586C12BAA8D9F42F5FE7D4B16B03A020CBC53C7630B55E1 + CB0173D17AEC5171DC3F86FA00F89E942DF0F640623C13CC1638B9A6A6E646AF + 63F11A12008C56AD5AE50502811AF671648B30CBF2D5FFDA75921D2A8DC7FEF5 + 652C8B9784A5C43F9FC2BEFB7A5E0E78B886E9B3C52F07CCFB009C7796850923 + 0D94EFCD5ED8E475448467086B0B4CFC976C81200190A0B4B474B2A6697D4548 + FCE3B8DDFE9C1BAE318C31F791EDCFD770DB9FE560C8281D63278A5F0E980957 + 9CF6570B13479BD8F32016EB46AF23223CA5D0C14F5F4A38F982AC442553C16C + 81DF5556D61CE6752C5E420200E55965654E629D2282ED8F47B02965FB7B3906 + 85AFFE4DAFA3223CC3B555CD98A0A1D700F1CB01AF5C25E1AA4E16C6DD65A025 + 35022212DD021D0CE8A763CEE36AC2D1244A9F00B205920040EBD6A1576C5B39 + 4B94C4BF84ED4F01A68E8BE1D2AE94444520B18A7A78AA6676EFAB39E13004C9 + A7FE637823A05EDD4DDC7DA7892C6A0444708280CD1635C79F13C0E29F646467 + 8B31F1A46C81ECF9D84824B2CAEB783CF90CBC0EC04BCACACA0ED334E51BC310 + A35B49CAF677EA89713CF9241B391BC8F6472021009E9CAB5ADDFB684E6EAEA4 + 895C0E9837021A74B38951430CA66425DABD2292F06B788E82FE43028996E6C1 + 80D70125E1B640CBB2FA300130CDEB58BCC0D70220140A2D9165792F11B6FE53 + B63F5EE8E5BD97EAB16F5BB2FD112E6CF0FCE01505BD07EAA88C48896C6A51E1 + 7D00A68F37D0B33FF50120B6806FFB673938A67D30B10B9097EB7540495C5BE0 + FA582C56EE475BA06F679870387C29537F4F5A9618AB7FB7DE3FAEB9D2C27D93 + D8EA693DFB6ABC3F952044800980CFDE52D0F736F1AB017201306B8A81EE7DA8 + 0F00F13BD875FCE1AB323A750F62D326B6D8C9112321D0B5054EADA9A9E9EB75 + 2CCD8D2FEF506EFBD375BD96ADFEB34458FD6F4EFCDBCFC687AFC420F1151ED9 + FE881479C08F5F49B871B08E8F3E555090EFFD35FB674422883D34C994AFED67 + 6A240088FF2291D06AE3F28E41BCF6B6025E1D500C5BA07FBB05FAF20E0D8542 + 53D9EABFB728B6BFD499D8C4D194F847FC0139C0AF6E35C05705AE06C88BBDC4 + 1A1C73D66453EAD0C552E93A26FE875CE0A76F80BF9E9F95A81A29922D10887F + 5F55153DD8EB589A13DFDDA1A2D5FBE76CDC081CC056FF9F7ECA96FDECA68018 + A71284280481D535C0AD77EA78F44971AB01261A01290E1E9C68E0FCCE246489 + 3FC0B505DED24FC7C382D902358D1F05C43B452291855EC7D25CF8EE0E2D2F0F + FD13500E12C5F6C78B63F033DDA7E634E0D8336C1A3489FF8557035CEB56039C + A3A178373105006F04142A7130FD5E6A04446C0526689D7AE0B8B303F8E12719 + 5959A9EA7C5EC313026D5EB9A275341A5DE97534CDF38E7D04B7FDA9AAFC8D28 + F5FEB906310C09679E6A61FE42367AAE93296B9AF85F7835C0B8832123C5AE06 + 986A0434E51E03479F4A0280D80A850E16CE5570F31D62D9027942603C1EF78D + 2DD0377768381C6EE538CE6722D9FE366C849D9B6B37BCF38291BDEF61543695 + F813DC6A80D32768E82D7035C075EB251C7F4CB211D0FE47502320622BF06DFF + 6C07C71C13C40F3F0BD72D7083699AE1152B5664FC88EC1B01100A85FA689A36 + 4518DB9F99489A72AEBBDA34EF9E60EA64FB23B6CA6FD500EDB2306FB1221EBC + 11D059A7599830CAC4EEFBB308EBBC8E88101A764D7FFCBA8C0E5D83A8AF17CB + 16C8E689E9D168B4B7D7B13435BE1000454545B96CF28F3275972BC2EA9F8750 + 5F9F4CFCFBE8D518A420C8F6476C1DB71AE0B57D7536504215B11A60AA0FC0BD + C30DB428055DD3C4D6492404A66C812A74DD11CC1688C3962FAFFECEEB789AF4 + BD7A1D4073C056FFD35455ED2582ED2F55EF5F6117FA8CFB62E870359D9512DB + 01AF06F8AA5B0DB05ACC6A80CB574AE8D5CDED035008EA03406C9B5CE0E76F79 + B7407DD3C63A3998970B5980359ADB2D30BEB8B2327A90D7B13425193FF38866 + FB4BD4FBB7DC7AFF4F50BD7F623B71AB01F6B955C7BF7E11B31A60B456C2D05B + 0C0C1FC21B00909D95D80E5C5BE0EDFD35E3C147344D552109660BEC1C89449E + F43A96A622E30540454568B1E328078862FBDBB03159FCE2ED17EAB1CF5FD88B + EBBC8E8A480BDC6A80370DD6F1A1A0D5007919E0BB871BB86D9049C296D87EDC + 23D0F66706F0E3CFC2D9021B2CCB2ACAD48440213EE6A6221C0E77662A6E8150 + B63F53C219275B58F0548CEAFD13DB0FAF06F843B21A203F2F2D2C1053004CB9 + D7409F5BA81110B183143A78FA110537DC12488C93FC884B841DDB4C4F08CC58 + 01C013FF54555D2949724084912865FBCBCEB11BDE5F14CBDEE75090ED8FD87E + D82A694D0D3050D06A807C20A98E20366BAA2975EF6DEA94D742EC103CA935E8 + A053A700DEFA40495407142921B0BEBEBE60F5EAD5EBBD8EA7D1DF9FD7013415 + A150683A13003D4548FCE3A46C7FD77735CDB1E3C9F647EC206E35C0DB866B98 + 21603540EEAE350C58B32619D2255D2C850400B1C314004BBE018E3B2B0B0D31 + 0979B962E46DF18440368FCC8846A3BDBC8EA5B1C9C8BBB465CB96F959595989 + D375D16C7F1FBF160374508634B163A4AA018ED23066829E28B92B12A93E000F + 4C3070C165E46C217602372170E04D1A1E7D424B8C9BAA000981A95D00CBB20E + A9ADADFDA7D7F134EA7BF33A80A680AFFE1545E9294AE21FB7FDF1EDAC07C6C7 + 7049171A1C899D40F06A805CE086430EA6F13E00E7D0354EEC243C219089C9DD + 0F096C5AB55A0916E43BC2D802D97CB22C1289B4F13A96C624E3EED2F2F2F243 + D8AA3F51BC4184D53FD9FE8846A3D0C1ACA99AD1A3AFE688560D90F701D8674F + 1B53791F80D34800103B892B7407DFA21933E788650B545930A6695E1E8D4617 + 781D4B63917177295BFD2F63ABFF0A5156FF1B364808061D7CFC6A3DF63A1C64 + FB23761E26009E78588DF7B8497772B2C5AA0698E803D03E8EFB47531F006217 + E1C75DA66B0BFC978CACA018B6407E14C0E695187BB4AAADADCD8842D7027CAC + 8D079BFC2FD3346DBE30F5FEAD6472548F2E26EE996892ED8FD835DC6A807D06 + EA58265835C0B5EB249C718A8589BC0FC001D40780D845D8B5FECC3C05D7F70F + C07684B305CE8C46A33DBD8EA531C818015052529223CBF26AF6D045D8FA4F24 + FE3500FBEF63E393D729F18F6804D8A0F8E99B0AFADEC6AB014AC8C9F63AA0DF + E07D00BA74B630EE2EEA03403402295B60E700DE7A5F3C5B60A6F409C81801C0 + 56FF335555BD5E14DB1FEF711D6093FE94BB29F18F682452D500EFD0F1E12762 + 5503E47D007A7633710FF501201A8B02E0DFDF02EDCFC842CC10C71628493213 + 23D64F959535FB7B1DCB2EBF17AF03680C8A8ACA0ED3757CC37F1665F5CF33FF + 0FDEDFC6871FB1A5D026AA8B4E340239C07FDC6A80AF0B560D305A2361E840EA + 03403422AE2DF0D69B35CC5B208E2D90C3FB0458967D5875757AEF02648400A8 + A828FDD1B6D5FD1CC7FB03F63FB4FD515954A231C8025656030387E998FF8C8A + DD5A8A7351F132C063EF3270FB60EA03403422DC16C8C464689F60DD868D7216 + 13BD42D802DD84C0AA68345AE1752CBBF43EBC0E6057292B2B3B4C55E56F44AA + F74FB63FA249E0AB1FC5C188511AEEBA5B47A8548091D085FA00104D826B0B9C + 3D4D35068FD0B5B80D891FAD8A809B1078051301F3BD8E6567496B01100E875B + 398EF3B52CCB15226CFDA7BAFD0574079FBE5E8F3D0F03D9FE88C6C34D8CBA7B + B4864123F444E11D11E0D77D75B58499130D5C7F9349F92E44E3C2AFFB3C07C7 + B50FE2FB1FE5842340205BA0C11E2DD3D51628C0C7B8F38442A1BEAAAA4E1625 + F12F65FBBBEE6A13FFDFDE7DC049555D7F003FAFCD6CC5454567669745452C44 + 63054DD4C4925862494C34F686A858FE622F5841C00A621701C5882D89694613 + 8D2D62A246B18480228A5276661405846D33F3CAFF9EF7DEAC0B2CB0C0CEDEBB + 33BFEFE7B39F9D1D6667EE0CFBDE3DF7BE73EEBD653CCAFEA08B85A3A109B758 + 74F1D511AAAD5523298AFFE679AF8B07C767E9D7A7DB0800A06B857FF7F9B240 + FE9B2F57A82C3097CB3D944EA7CF91DD960DD1638FD4B0EC6FB1F82A5761F4BF + 52D9DF3F32C16216C88486AE56E3D1EF1E35E99C8B2364596A2445F13E0046B8 + 0FC0CFB10F001402CA020BD37ED90DD850B158EC21CBB2CE5261F4CF7F03BC16 + 3A7F7F6842867E79324E8250202200F8F31362247469841C47A38825BB41C1DF + 7E6DC2A3FB6ECBD28FB00F00144A2FA2793389F6FA49B9083AD52A0B340CFB93 + 050BD2DBCB6ECB7AB75D760336846A657F9CF86787897F4F21F10F0A490400FF + 7C3E580DB021A5F953A1B23535130DD8C6A37B6ECDD2DED807000A252C0BBCEA + 128B1E7AD4F267BF54D927202C0BDCB5A1A1E143D96D591F3DF248ADAF8F7DE2 + 38E60055CAFE1A9B7855368FA63FDF42DBEC2CEE5C2EBB5550B4145C0D90F701 + D88FF701189BA5ED77C33E00504051F2F709D8FB27519AF3A972FB042C320C63 + 97458B162D91DD9E4EB75B7603D6573C1E3F49445BD344B425BB293E1EFDF374 + D42107D8F4C4EF3248FC83C2AA269AF3BE46178D88D0744556035C2A46FC871F + 6CD3B8D139AADF0EFB0040018509817F78CCA0F3AF88FAF9276551D98D0A8465 + 81FF974AA5EE95DD96CEEA51014098F8B754D3744B8542E3A0EC8FDCB2A8D7F2 + E68BAD95DB0C147736930A4D8362955F0DF01A8B5E7C558DD5007923A0837E6C + D39D6372D47F27D19E46D92D82A2C6D3FE151E0D1A544673E7E94ACC82B17016 + A0457CF5E92965813D2A00E0C43FD334CF5261AB5FC6A54FB643DE7967E6ECD1 + B7E52C8CFEA1E0CA89BEE1D5006F88D0E3BF576335C0FC4640B78FCC52CD9684 + 8D80A0F036219AF1BA46871F5746992CF9418002E9602406A83C0B30299D4E9F + 2DBB2D9DD16302803E7DFA54452211FFEAA20A897FF9B2BFED07B8F4D64B9920 + 2A45D91F149A82AB01F24640C34EB7E9D61BB354818D80A03B840981271E1BA5 + 175F35FC925895CA02B359DA75F162F513027B4C002046FF93C4E87FA80AA3FF + 7CD91F7F7A932764E8E893B1DE3F7493B01EFAE631168D50643540DE08E89ACB + B274136F04A4612320E806DC738951FFE7B389061D544E395BB9B2C04F172C48 + 0F90DD9675B65576033AA3B6B6F6FBE29B1F4DA930FA6F2BFBFB7158F6D782B2 + 3FE8260AAE06C8FB00DC372E4BE75D826580A11B85B300575F6AD1C4A96A9505 + 9AA64ED9AC7D4A3A9D9E26BB2D6BD3238ED6442291D2753DA6CAE89FD7FBB74C + 8FDE7DA585B6DA8950F607DD4BA1D500F3FB003C303E4BC32E460000DD8C2B00 + C4606CEF83D42A0BE4AED5F3DC9C61183195CB0295F8A8D626168B9D6259D66F + 5458F18FE5D7FB1F76468EC6DE81F5FE41021100FCE90983865D1A21D7D1FC20 + 4096FC3E0013EFCCD2B1A7611F00E866E18CD81FA71974EEE551B2C5DF6254AD + B2C00B53A9D43DB2DBB2264A1FAD5CF6273EC4659AA6992A4CFDB725FE6DEBD2 + 5B2F23F10F241101C0EBCF731D74841A92725703CC66F984EBD183E3B274C471 + 5805102408CB02F71C54469FAA5716C835319B8920A059767B3A6CA3EC06AC8D + 18FD4F364DF34C15A6FE5986F7F811A3ADFBEFC8D0D12721F10F241101C09BFF + 085603FCF473B9AB01F2E89F0F02DE09107B608014FC27D78BE8FD37343AF498 + 327F7120C5CA02A7A4D3E9A1B2DBD211658FD63E7D12BB4622DAFB7C5B99D17F + 0BD18EDBBBF4EF7F8BA0AE19D9CE204935D1EC77351A3E22426FBD6350AF6A79 + C707CF88C5FA7874DFED593AE04804002049BBB2C0175E35296279280BEC4CFB + 6437604DFAF68D7FE6BAC636AAACF7EF97FD0953EECED02F30FA0799CA8896A6 + 83C5801E7BDAA4CD3793F787C8FB00ECBB77B00FC00EBB631F0090847BB272A2 + CF3F261AFC93B2A65C4EABA8AA244D81B1A35F16A8EBCEBC850B53FD65B765B5 + B6C96E40476A6B6B77310CED0395D6FB47D91F2883AF79EA1E5D2B0280317758 + 524B01177FA3D1C9C7DA74C7A82C6D9A1077B4C8FE70A06485B300D75F69E5EE + 9D6C99A6499A626581A7A6D3E9C764B7A53DE50280BABABA4D5DD79DA5695A4C + 85A9FF7CD99F6978F4DE6B2DD4EF7B84B23F90AFC6A3A90F9874DE6511EAD58B + C89030DDC953AC8B1651EB2D2373FA95D7E722A88801E9B802400CCEF63E50BD + B24022CF167DDA262A25042AF1D1B4178FC72F14A1DB5DAA95FD9D3B2447636E + 47D91F28A286E8BDD735BF1260D64786BF0A5A776B6AD6689B7E8EF3D0842CED + B9BF6B784B35454EB650B2C2B2C03F3D6ED0900BA3FEDF2307010A8C25954C08 + 54EA700D77FBFB467C455518FD73135A5B89B6E3B2BF5732C132AC28FB0315E4 + A73BAFB2B237DD6AB97DEBA8CCE9C6C03418FDEB74F1F9591A7F5B5604C5488A + 0545F079BADCA3E38F8FD2CBFF34FC85B2544A08745DB752955900A50280582C + 36458CFE87A854F6C77F3C0F8ECFD02F4E44E21FA8838353ADB747F3FFA73BA7 + 9F1BA1E96F1B467C0B8FDC6EF8FBE4F3186F01BC55BDEBD7FFEF7B28B2FF4131 + D544F36713EDB67FB9E87035AAAA94BF64360B67011E4EA7D367CA6E0B53E6A8 + 55ADEC8F3589186DDBAD5D7A7746ABF801231C50508D47CF3C66D2399744C871 + 784D80C29EE8B8F3E7ED57790BE009633334FC4A7150AC40522C28269C211B71 + 99450F4FB3FC6342E692D979DA77D7C876696868F8AFF4F6C86E405EDFBEF179 + AE6B6CAD4AD91F77FEBCE8CF4B7F68A1EFFF8082F22635E212009F3F0BC0494F + E2EF74F44893AE1B6B519FCD892291C25CF3CC77FE2BC4B170D6A939BA7D944D + 659B89D75AA14AA215403BFE92C01E25FA973736366995BDAA55290BE47D02BC + C5C964720BE96D91DD009648244E334D7DAA4A657FB99C46071F68D313BFCD20 + F10F94C553FE7A35F9B35323AE35E9E67111AAEE45B48918FD74E59534EEE09B + 9B355AB6DCA3F386049D7F3977FE8DE8FC4151E12CC00D575BB97B265AA669A9 + 5316C8FB04B8AE7B5A4343C36F64B643FAA11B96FD7D259A62A830C4E6935963 + 1379A6494D335E69A9EAB73D05B5CDF29B06D021EEE8F51A714374C6D39E3668 + CC388B3E9EA3532CD635ABA1F1F37FB958F3838A1BAFCCD1F0736C7F2F765222 + 8D09602D78A32C71EEDEEB8028CDFD4CA73245CA02C35900477CF592991028FD + A3E0B23F110DDDA54AE25F58F6E7FDDFD9397BE4CD390BA37FE809FC20A04ADC + 303D7AFB55836EBED3A4675FE074688D36EDED5134123CA633273F8E75F570BA + 7FC952BF7E997E7E9843179E6DD3FE07BAFED4AAB75C8D1329C05A8565817F7E + C2A0332E8892A62B5716F8483A9D1E22F3E3914674FE3C8E582A3E88880A897F + 2B95FDBD9A09A69050F6073D849F13203A7A5E2970D99744FF7ACBA0E75ED4E9 + 99674DFAEA2B9DACA847112BC86D89463C328CF077B460AD0B2E23E4149CE616 + 8D5AC5D7165BB8F4AB236D3AFC6087F6D9CBA51A5EE94F3CCE6B45E70F3D4858 + 1678C2F1517A49C1B2C06CD6DB6DF1E2E40752DA20F3031001C02362F47FBA0A + A37F7FBDFFD6E00438F5BE0C1D7502CAFEA0E7F13B743EB95592FFB7FB6D8A28 + 99D669E66C8DDEFFAF4EF3E66BF4E93C8D66CFD14527AF070F12A781EA5E2E6D + D6DBA5F2728DF6FB8143878A91FE0EDB799488B9B4499C82334553707CA0F387 + 1EA79A68C14744BBFCA85C1C23EA940586FB047CB170616A6B29AF2FEB8D6FBE + 7962B768547B8F6FAB30FACFAFF7FF93FD1D7AF249ACF70F3D9B1F08F0C88767 + 04CAC40F3931B2FF5AF397B55EBE22A8E36FCD0453FD9C48585146148D06B302 + B12D3CEA5DE7057B0EB48A0764C5F339E8F8A1070B1302AFBDDCA2498F597EDC + AB425920E37D021CC7DBB5A1A1FB770B947648F7ED1BFFC2758D7EAA94FD3536 + F132A61E7DF87A0BD5EF48D8D50C8A465B30C0095146FEAB83A0DB0D4F075942 + A70FC587CB02C5397ED3DA8A4631D853AD2CF0EB6432D9A7DB5F5BC61B4E2412 + A78BA8E71155CAFE78E4CF9F04D7368FBE0DEBFD0300149D3021F0C929666EF8 + D5115374FE1AAF99A1022E0B741CE70C11044CEDEE8FA45B71E29F8878560457 + 2AE5875F3CC259BE2228FB5BD2D05C459E86C43F008062C4D3FE551EFD60AF32 + FA78AE3ABB0586B300AEF8AAEECEB2401901C05411ED9CA642E21FE35227BE0E + 3AE1E6AC7DE299B689C43F00802215CE02FCE549834E3B3FEA5703A85416E838 + CE5411009CD19D1F47B7512DF18F9BC099FF3B0C70E9CDB7C58D46ACF70F0050 + D4142F0BCC64BCDDBFFE3AF97EB7BC6677BEC120F14FEFA742E79F2FFBE38988 + 4751F60700503AAA89167E4CB4F3BEE5C4DDA03A65819A0846DCF90B17A6B6EA + 96D7EBAE3796482476354DFD7DDBE64F59FE278DB23F0080121596055E77A545 + F74EB2FCC5B1D4D8274023D3D444DFE4EE964C167E71A06E0B00E2F1F837BAAE + 6FAACAE89FCBFE381099F9460BF5DD8150F60700504AB82C500C04F7DA3F4A73 + E7E95416552721D075DD6F2CCB1AB060C182A5057DADEE784362F47F8661180F + 3B8E1A436C7FBDFF1CD10567E768D42D28FB03002839ED1202875E1425D7E1C5 + B064372AC06581B66D0F4FA5527717FA2328A87CD99FF8D25518FDFBEBFD6788 + 066CE3D2DBFF0CEBFDB2B25B050000DD8EA7FD2B3CDA638F329A375FADB240D7 + 75C5E8946A0A5916D81D01C0A3229A395599B23F5EFED4209A7257868E3C1E89 + 7F0000258B7BC02AA2D9EF12FDE888727F16A0A242A9B2C0DF8800E0B442BEFD + 82119DFFEEE24DCCE0DBAA8CFE39F37F9B7E2ECD784FDC6842D91F004049E3B2 + C06A8F4EFA75945E78C524CBF2942A0B1483E73D4410F05E415EA3906FA0BE3E + B6C0718CBE2A74FEFC59363707A3FFD7FFDA4203F71477361246FF0000A58C7B + C172A28573FDDD021B3D972A2B2BD5D92740D79D050B17A6FB15EAAD17442291 + 186218FA14C75163EA9FAF40E4721A1D72A04D8FFF3643B402657F0000406D65 + 81234758F684072D539DB2404E08E44B01EE99C964F2E1AE7EEE820400F5F5F5 + BD6DDBFE261877CB0FA3B8154D4DE469BAD7F4E1F4D6AABEFDC59DADA442D300 + 004005E1C640837F1CA54F152A0B0CBA69CF354D73F3AE2E0B2CC8DB8BC7E3C3 + 0DC398A04AE21F97FDE57244170FCBD9D78FCD9928FB03008095846581CF3E65 + D0991746C91503C4A822BB0586098117A552A9BBBAFA2D77A9B0ECEF5BF165AA + 70EDDF2FFB13A3FD01FD51F60700006B119605EEBE47197D5E0265818508007E + 2346FFA7A830FACFAFF7CFEB0F4D7B3043471C87B23F00005883B02CF0A31944 + FBFDACDC9F05A82857AA2CF03111009CDA956FB7CB88CE7F0FD1C877F9B60AA3 + 7F3FF1CF263AF800879E7802EBFD4389EBECD12EFFD0059047FDB2C03D451030 + A34B9EB32B1B585F1F5BE838469D0A9DBF9FF8D71C0401B3DE6CA1BA011494FD + 01949260CB0B228B82B5CFCD4E1C9BB6F825BE5A966BF7FB00A582FFE6CB8816 + 7DE6EF16C8BBC4545556A853166818CEC2050BD2F55DF556BB8418FD0F354D63 + 922A657F76B8C0CFB021391A7933D6FB8712E45FCF145FBAE757BDBCFF8E4E9F + 7EAED1279FEAF4F127D4FAF5121ED6E8E254E7B66EBEA94B3B6C4765DB6FEB52 + FFAD3DDA6D90EB9F04C9E50534080B66416909CB02475D63D9773EA05E59A06D + 3B67A552A9C91BFB5C5D120084897F8DE24B5365F4BFA291BC68841ABF5AD85C + ED9FC432B25B0550787CF8693CD2AFF0FC64D7D75F32E8A93F9AF4DE873A65B3 + C1AC585393462B9AC8CD66353E5674F13B6E24E2517525E995951E89D10E4522 + 447BECE2D2F1BFB469BF839CA044AA59232FA346521440C1E5CB027F141581B3 + 3A65813C0BE005AA363621B0AB0280C70CC3385985C43F962FFBBBE4BC9C7DDD + 6894FD41F1F33B7E1EA1547B94FD4AA3498F19F4E22B06CD11A3FDAFBED6A8A5 + 45A368D413A307F2BF4C63E59319FFBEED0409B3FC95C9685451EE519FCD3DE2 + 5981830F74E8AC531D8AF4F1FC45B43C5B8D932140C1846581CFFDD6A093CE8E + 92298EAFB232A51202A78900E0948D7D8B1B2516EBBBA761B8EFF06D1546FF2B + EDF6F77A26E8F873B25B0550389CA9ACD778FEDFFA9F9E32E8DEC926CD9A63F8 + 4B5FF368A1BC2CE8F8D7E7F0E4CE9D038196567FB4E1CF0A0CDCC1A10B86DAF4 + 0BAEA6D1C5CB2DD348471000C58C130245207CE289517AF155C3BF0CA0584260 + E5C6CC026CF4E1AB5AE21FD7FCF348E6F189193AFCD728FB8312203AFFC59FE9 + 74C93516BD32DDA0C6A6E07A254FE377C5289D0F6DBE7CC0336B55954407EEE7 + D09D6372B4797F11712C430400458CFFBCC5DF7CC35CA21DF7AE108174100C2B + D0DD75C92CC0461DBDBCDB9F691A335C3104512100C897FD1D7280438FA3EC0F + 8A5D98A8F4E63F74BAF4BA08CD9C6DF823FD8A0AAF204B7CF211DEDCACF93303 + 3B0F7468C2D82C0D3ED0F577D5F4B2B82400452A3CCE6EBCDAA2071EB6FCBB4C + 051202BB62B7C08D3A64138904AFF8D74B85CE3F5FF6E7381A7DF45633D572D9 + 9F180961F40F45293C29FDEB059DCEBB2C4273E719D4ABDAF34F4C853C1CF938 + E30A1BBE345051EEB48EBD2EA79D31C48DF2E5073F0890FDB900140227D6EA1E + F5A9AB5891CB515555953A65812200F8560400351BF4FB1BFAC262F47FB66118 + 13554BFCBB68588E6E188BB23F284E7EB21F5F97149DFF1BA2F33FFFF208CDFB + C2A0AACAEE5FACE4DBE59ACBC7DC139332FAAF4EB1FD63CE7330130045280CB8 + 475F6BD9E3EE57AB2C30BC1470B6080226ADEFEF6ED0A11A96FD3585E508B2DF + 7F5BE2DFB6DBB8F49FD75BC51D1AD6FB87E255138EFC2576FE8C3BFAE52BB894 + D06D79E2A18C7EF8AFDD28726EA068F1ECBF38CE06EF573C65811BD4FC582CF6 + B8699A27AA32FACF84538F8FDE97A19F21F10F8A949FEDDFDBA3F7A7EB74EAB9 + 11FA6281BCCE3F8F5FFBABAF3577ABBE0E3DF36856DFE9072EB94B342532A501 + BA545816F8FC6F0D3AE1ECA83F03C041800A5D0DCF02D8B6FD783A9D3E797DDF + D27A119DFF20C330FEC3B75519FDF3863F5BF773E9BD19ADFE622558B50C8A0D + 1F695A0551EB52A28B4658F4D0548BB6DC428D35CAD9374B35FAE98F6D7A707C + 8E6A7774C95BAA29313A02E8526159E0492746E90505CB021D471F944E2F7CB7 + D3BFB7BE2F545F1F5BE43846AD0A9D3F37BEB935B8FDF68B2D34601742E21F14 + A770F431F50193CEB9284A35359E1299C8795C19B0F86B8D6E1A91A51157D941 + FE0D02712836EDCA0277D8AB421C83EA940586FB04342C5890AE5B9FB7D369B5 + B5B5E7E8BAF6A02AEBFDF315886C4EA3C30EB269DAD3197F853294FD4151EA45 + 346FA646C32E89D06B6F18B4451F4F89934E5E3E1F20B6854B8FDE9FA51FFCD4 + C12C0014A7302170E435164D78C0A288A54E59A0E89F2997B307A7D3E9773AF5 + 3B9D7D72D512FFC2B23F6E4AE3EC375BAA135B93BFE10946FF5074C2D1FF03E3 + 4DBAE08AA83FF5AF6AC79A4C6B74FDE5391A796DB8FC266601A018F13E019A47 + 83F72B532E21707D66013ADD64D512FFDAD6FBBF20E75C372A67A0EC0F8A5639 + D19214D1C5D74468DA6F4D8A6FA9D6E8BFBD65DF6AB4F79E0EDD7F4796B6DF43 + 34F25BD92D0228807C42E0EF0C3AF5BCA87F573422BB5101DE2DD075BD610D0D + 0D133BF336D68947FFBAAEF3D5752546FF6D657F5BBBF49F37C2F5FE51F60745 + C8AFFBEFEDD13F9F3768C805115A2602DDB2A8EC56ADBDBD8B176B74EF1D193A + 67B88D8A1C285EFE76DB1EEDB55719CDFD4CF7370A52651680FBE96432B9CED6 + 74AAB962F4FF84611827A8D0F97383B9F3E71980A72667E8B06350F607458C6B + 8F7344D78EB1E8960941E6BFCAF80498FA52A3538EB369FCE82C6D1A1777B6C8 + 6E154001706754EDD1F3CF1874C2596564591E9545E477451C00F0976DD3E074 + 7AD15A7301D6190088D1FFBE62F43F9D6FAB1000F8EBFD8B13E2A1073934ED71 + ACF70F45AE82E8CBF944975C1BA1DFFDD9A43E9BC93F06D78503F49A4D3C9A38 + 3E4B3FF9B9830D83A078F12C409947279D14A5175E3688570854A52CD0309CE4 + 8205E9DAB53E6E5D4F2446FF7F11A3FF2355E8FC7974C15B9CE66C8DE6FCA799 + 12FDC59DBCEE91FCA60114468D472FFFC5F0B3FF972CE364A39EF1C7CEB38F8F + DC9FA5D3CFCD210080E2C57FDA22484F7E46B4FD6075CA02F31501B6EDEC974A + A5DE585BF3D7A85FBF7E35B95C6EA92A99FFBC090937E382B37374FD68ACF70F + 2540040053EFB7E88CF3239448C83F063B8B03803B6FCED24557E13885221796 + 058EBAD6A23BEF8B5024A2C61A1DDC6F3B8EF36C3A9D3E6A8D8F59DB13D4D6D6 + 5E2CBE8D57A1F3E7D17F631379BA4E8D4B52CDD5E4883B32B25B055040E189E5 + CE9B2DBA6444CF0A00D25F6974D9F9391A7B5D8E8C7242922E1437AE00D03D1A + BCAF3A6581F981BB6559BDE7CF9FBFACC3C7ACED09128984239E44572100E0D1 + 7F569C442EBB30EB5C33D246D91F143F7152C936125D7D9345774DB468CB3EF2 + 8FC3CE5ABA4CA3230FB569DC4D39AADBD60B2ED50114ABB02CF06FBF37E8E461 + 51BFF357A12C309805F02E4BA793E3D6D4EC0E71F29F6118D3B9F3971D00AC56 + F6C7497F39A94D02283C318A681671FB55232D7AE0118BB6D8BCE70400BC1EC0 + 21077125408EB6DAC10B96E80628666159E0EEBB97D1170BD4280B0C7201DC6F + 172E4CD574F8EF6BFA4511003CABEBFA11B23B7FC6BBFDE5B21A3D3DA5950E45 + D91F940A1100B47E2B02805116DD37D9F297FFED297806E0673FB5699C0800FA + 6D8F00004A40B84FC067FF23DAF3C072D2C5CF1C04486D92162CC7EDBADE4F92 + C9E4CB1D35B943B5B5B5FED9467600B05AD95F2B76FB83122102801611005C8D + 0000A067E0DD02AB3D3AF9F828FDED25932296FC1D3BC35C80EB450070D36AFF + D6D12F24128983C42FBDC4B7650600E17AFF2200D068EEBBCD14DF8650F607A5 + 4304004D4B82198007A7F6BC4B003F3D20B804B0CD4004005022B84715A3FED4 + 17BC5B60F90A43A7AA8A0AD2648EA33900705D37974AA556CB4A585300709DF8 + A551B247FF9CF897C96A74D90559BA76542ED8ED0F897F502AF89AA2E1D1E831 + 165D3736428978CF0900167FA3D19927D974EB8D59EAD587828DBA004A01CF02 + 547934E646D3B9FDEE88C1C98032CB02F32B038A2060B5CB001D0600F1783CAB + EBBA253300E097CE6488FAFB897FE2ECE16A282582D253E3D183775A74EE2511 + E2AB720AA4E4740AAF0330E6BA2C8D182102F71C56EB8412E39705120DDE374A + 9F7DAE53547259E09A2E03ACD6A4FAFAFADE225258223BFB9F4BFE5CF1F28F4F + CCD0A1BF42E21F94281100FC619A49438747C832D5D8777C5DF844D7D0402D0F + DD9DD1CFFA3F278A9500A1E4F09F7C2F8FFEFECC776581118965816BBA0CB0DA + 912946FF23C4E87F8CECD17FAB18F46FD5CFA5F766881BCD48FC8312250280E9 + 7F33E8BCCB23D490D4A9BC5CFD28B8451CB2892D3DF7FE3BB2DA8F7EE6680800 + A02471B02E8ED77DF62DA38FE7EAFEBA00B26601F2970144DFBEE982050B96B6 + DDBFEA0363B1D84B86611C243300E0CE9FB3FFDF7BAD85B61E4848FC83D25546 + B4344D74D9F511FACDD3668F4804E404C003F6B369C2D81C6DBBB3686FA3EC16 + 0148C0D9FFD51E8DBBD9A45B2744FCCE5F762E80E37897A7D3C93BDAEE5BF541 + 8944C293B9F6BF5FF62746FB871DE4D0634F6582C43F5C3F845256E3D1FD7798 + 99F32F8FB8757554EE2A9C081B4CFFEB74CD65191ACD89BB0E66EFA084F176DE + 2210E89D285F2E7ADBEA4A8915011D2D0AB45200108FC77FAAEBFA8B7C5B5600 + C0A37F8E923E78BD85E2FD2858EF5FFD410F40E1880060D65BBA77FE15116FC6 + 0786DEAB5ADD0382A7FF79CBE207C767E98023B0153094B8703F8F5B4799CEF8 + FB225C1F206D1620BF28D0A2450D6D07E54A47A718FD8F110F1A21ABF3F733FF + B3443B0E70E88D3745CFDF88D103805F5614F5E8FAEB2DBAE9D628D5D6BA4A56 + 0304A37F8D4E3BD1A67B6EC952F516E2CE16D9AD0290887B588B68E962A21D06 + 97FBA5EDB25607D4C20404D7750F4EA552FFC837AF4D2C167BC1308C83650500 + FCE1708B465C9CA5E1973AC1B57F85A73B01BA4D8D472FFFD9A0732E89D09265 + 3A9597A91701F0E87FD3DE1EDD7F7B960E3E1AA37F001F8FF82B3DDAE78751FA + 78AE213D1950040063440070ADFF73FB7F947DFD3F3FFD3FFBAD16DA2C4641DD + BF7AE73980EEE75F4BF4E89AEBCCCCD87111B7B696CA559A05E0E54E172DD2E9 + F2E159BAEDD66C70ED1FEB76000497012A88EE1A67D0D83B237E9F26F33280E3 + 382FA6D3E943FC9FF3FFD0A74F9F58241249F16D1901C04AD3FFFFCE103561FA + 1F6025612EC0D0E1412E00EF0DA04210C0A3996F9668B4FB2E0E3D746796BEB7 + B78BD13F401E1F0AA2DFFF46F4AE037F20FF3200F7EF495EA98BDA050062F47F + 84F8C767F9B68C00803F143E915C8DE97F808E857B8E3FFF7B83CEBB34B814C0 + 0981B283003E7697AF203FF1EFB4613616ED025855781960DF1F46E923899701 + F27900B95C2EF6D5575F7DD9D684783C7E93AEEBD74A9BFE1783FE7211157D38 + BD8536DB925B48388900B4C387A6C6EB788913C9A4BB4DBAE08A085555697E3E + 80BCD2A260D5BF4BCECFEAE36EB7FD25CFBC16F9FBA00328A5FD6580F1C1627C + 322E0368DF1D98873734343CDFF6532C167BD1308C9FCA0A009A5BC88B46BD15 + E9544B2F7FFA3F27A519004AF38300712221DBA3D1B79874CB840845A3728280 + 7CD6FF513F73DCA9F765B4DE7D3DCD5BAEA1F3075855FE32409A68E0DE4A5C06 + B831994C8E6C7F09405A02A0E3040B001D7D84ED4E9C94D3FDE97F2CFE03D021 + 3F08281737722208B82D0C02229A38A174DFB1CBC76B3AADD39187D9F4F03D59 + DABC5FB8E52F66ED003A165E0638E0C751FAEF6CB997015CD77D2E954A1DD1F6 + F2B5BCD518C9B9FECF2BFFE9A22523AFCED239E73AC1D6A1B8FE0FB0761C0464 + 3DBAE9569346DD1611018046DDB1485063934639117C5C71618EAEBDD426B39A + 8205BB70CC02AC19AFE7514134FC028B9E7CC6F42B670CA3FB9B110EF4ED6432 + 69F90180EC0A80FCF5FFF75F6BA13E09C2F57F80CE0AB71DFDD7EB3A0DBFDAA2 + 19EF99B4E596AE7F72E9CAD1857F5A10CFF7D5628D12318FAEBF3C4B434F1191 + 7B9586913F4067701E80E8E7EEBFD7F00376DEEDD692940790AF04F04F1162F4 + 7FB8F8F657BE2D2300686E119F4B9428956A265CFF07584FE2D8E1D145CB12A2 + 5BEF3269F4388B4C43A39A4DBC8D0E0478AA9F7FBF35A3D1D2255ECBD147D9DA + 84317659FDB65E30E247AD3F40E784AB022E4E12EDB67FB9BF7016F77BDDDE8C + 3000C8E57271FFD4207309E07CFDFF2EDF73E895D750FF0FB0BEFC9C009E4AF4 + 2F0910CD99ADD184074D7AF4299372B6469BF4F2561A69B40504FCDD5BFD76FE + 34C097E65A5A346A6EF25A060F76F4DB6FB4A3FBEDE37A5A543C9A8F5104EA00 + EB8717F4AAF4281EAFF067BE2BCABBBF09F900407C1D9D0F00C68A3BAF969900 + 78E2AF6C9A70772EB8FE8F0440800DC39704B8B36F219A250281679E35E9B1DF + EAAD9F7EA69319D1CAB8F4C814C1826505B303ED03007F27CE9CE61F932DCD44 + 5B6FE5D21927DA74D4618EB7FD008FCA36118FD283E7C6943FC006C82702EE1F + A599B30C8A4848046C1700FCD27FE9582CF677C3300E91990078D335593AEB1C + 2400026C343EAAA3C177A7D14FDAF3BE594AF4F97C4DFBF8138D667FA2D387FF + D369F137225E086706B236EFE247B4CB4E2E6DD7DFA57DF7F26887ED5C7F8AD2 + A80E9F571C9B9E831A7F800DC633756544C32F949B08C8447F7F533E00784904 + 0007C9080078FA9FCB21FEF4588606FDD0C5F6BF005D858F6EEEE0FD7547C441 + 950BD6E7E7632E238E33DB0997020DA7FFF94414151D7EC40AD71AE0D13E5FE3 + B709C7244057081301274D34E8BA31F212014337FBC77F7D7DEC6BC731364300 + 0050C4F4765F3CEA587524CFC71D5F7E73C3EF380E01BA56383BF7CEBF75FAF9 + 2951CA86FD9F0CAEEBBEE09F02FAF58BAFB06DBD4A6609209600060080A2D62E + 00F88508003272038097F24980AE26C808005A5A88AAAA88167CD12C7E400920 + 000014A9B01470D962A29DF691570AC84CD35D920F00A42D032C3E006F935E5E + E3E79FB754630D000000285A6100B062894703F7AE08D6C091B42780697A4DF2 + 038016F22A2BDD6F172E68ADA1660400000050C4782D80A847F5DB5450635370 + 095C0611007C2B3500E00CC85C9668CFDD1CEFEF7FCF6A7E7D31160102008062 + 152E06545F2F028046110048580C28E4C90D00DC601D801F0E72E82FCF660901 + 00000014350E00CA4500B095DC1900A64400B0F79E0EFDF5AF08000000A0C821 + 0008E403807DF772E84F7F4600000000458E03800A1100F4937E09406E00C0B8 + 0AA0CF66DE8A39735A7A21091000008A9A223900AA5401506525B90B1736EB28 + 03040080A21611FDDE728FB61B5441CDCDF2CA002DCB6D911E00B4B612555410 + 7DF65E33452A35EC2F0E0000C54B9100C075DDE97E00D0B76FACD5758DA8CCBD + 009E7BAA95761DE4612F000000284EDCE38AFE6ED6871A1DFCAB32D94B01BF1C + CE00C45ED734633F040000000005C21B7145899E7A42A78B4744C971782A5E4E + 534400F02F3F0088C7E3FFD475FD473202805C2ED886F4EE5B3374EC71E16E80 + AE9C0F040000A0607817CE32A2EBAE31E9A1472D7F46C034E43445F4F7E3F301 + C07811005C2C2300B0C36D47CF1B92A31B46D944AD146C450A0000504C4CF24B + 008FFE7984A6BF6592257ED675394D11FDFDB87C00708708002E951100302E05 + DC62736FC5C75C0A884A00000028466109E056FD2A68F90AB96B00B8AE7BAA1F + 00D4D6D69E2ABE3D2A3100E02D11DD8659CDBA16452500000014993001F0E399 + 1A1DF0F332FFF27754C256C0F98A3FC330B6CD97016E27EE9CC3B765E601DC73 + 5B868EF935F2000000A0C8840980BF7B5AA70BAF949700B85A00C06A6B6BFD9E + 5F661EC0B033723472B41D2C098C3C00000028167CFDBFCAA3637E11A1D7FE25 + EFFA3F0700ACA1A1C1680B00EAEBE39EE3E8520200C62B02F6AE21FA6C5E3321 + 0F0000008A0AD7FB673DAADBA9829A5BE46D02C40180EBBA6FA752A9BDDB0280 + 44223643D38CDD650500AD19F223A2379E6FA56D07623D0000002812E1F4FF1F + 9FD169D8C5517245DF169154FF1F5E0298904C262F6E0B00E2F1F87DBAAE9F27 + 2B00B0C35D00CF1F9AA3EBF9324023E132000000F47CE1F4FF2F8F8AD0EB6FCA + 2DFF0B67004E4AA5524FB49B01489C2EFEE111590100E36A80EA2AA2F9FF6D26 + B2500D0000004580A7FFC5B0BFEF8E15D4D42C77FA9F897E7EFB6432F989D6EE + DFF4DADA5A27FC47298DCB66B9814453EEC9D091C7B8444D846A000000E8B978 + A53F31FA1F7B8345773E60F9237FD394D3140E00F86BD1A2457EDFDF3E00A07E + FD129E6DCBD915308F67016A7A11CDFB00B3000000D0C371ADBF237FF4CFB8F3 + 771CE783743ABD9BFF73FB7F4C241213C503CE961900F02C00BFFC23F765E8C8 + 63DD201700B3000000D0D3F075FE5E62F47FBD45E3EEB3FC91BFACD13F0BAFFF + 3F984AA5CEF57F6EFF8FB158EC14C3307E233300606DB3003345B8C4958A19A9 + CD010000583FDCBB8AD1FEBC8F34DAEFF032BFD2AD4CC2CA7F6DCD09AFFF3B8E + 33309D4E7F946F627B7A5D5D9DC30180CC20802B02B239A2CB2FC8D18851E2C6 + 0A0D15010000D0738499FFC71E1DA17FBC66FA53FFB232FFD9AAD7FFFDFB567D + 503C1E9FA9EBFA4EB26701F2D1D2BFFFDE4AFD76F4828440AC0B000000AAE38E + BE8AE8B967743AE38228792E512422B74961FDFFA464327976DB7DAB3E289148 + 9C261E38557600C02FDFD8A479750967C5EC19ADBDFCF2005C0A000000D55588 + 2FDBA3AD0656D0B26F35AAAC903F7A0D13004F49A7D3D3DAEE5BF541757575DB + 8A6F73655F0660BC5942533379471C626B4FFE2E8B25820100406D61D6FF71A7 + 45E8EF2F9BFE96BF86C4A97FD66EFA9F5BD2D6B16B1D3D5895CB002C1B96014E + BD3F43871F2B2282E51AAA020000403D3CCD2F46FB271C13A1E7FE61524585FC + CE9F7534FDEFDFDFD1831389C419E2171E56210060F97C808913327404070148 + 0A04000095F0DAFE951EDD72A345B7DD6D916E1045255FF7CFEB68FADFBF7F4D + BF505757E7A97019206FB9E8F47BD77834FDB956EAB7B38B20000000D4108EFC + B9F3BFE35ECB4F59939DF497C79DBF697A347F7E72A5E97FFFDFD6F44BF1787C + 8AAEEB43540900FCA4C0468D36DFCCA357FF822000000024E31E94AFF9977DD7 + F9735F552671B5BFD59A184CFF4F4E26936775D4FC0E251289ED44003047A559 + 006EC68AC66026E08171614E0012030100A0BBF11AFF9CED9FF1E884D322F4C2 + CB2619863A237F964FFE735DD7DFFC67B57F5FDB2FAB940C98C72D69129D7EB9 + 88B82E1A96A3AB6E14BD7F6B5822A84E330100A01871AF196EF0337FA64E87FD + 3AB27C61835E5D5E4E9A257199DF0E9B1A8CFEA788CE7FE89ADECA1AA9B045F0 + 9AB4B6063302871C64D3938F6689CAB5EFF60D50AFB90000D093716FC957D179 + CA3F124CF9DF33C9E25969AFA2DCD378F4AF92758DFEF36F69AD54D821704D72 + 7610086CB629D1BDB785970432E16C004A0501006063E53B7ECEF22FF368DE07 + 3A5D35D2A457A69BE4887E86EBFCD7D991CA6876D0F9CF4EA552DF5BDB5B5BAB + BABAF8502263927822D9EFA743FC1FD0DAA25134EAD18F7F68D32D37D8B4CDAE + E24E711F89000133020000B0DEB8D3E71EB25DC77FA5E8F8FFF5B6E997A65B92 + 77F65B9BFCE8DFB6EDEFA5D3E9D96B7C5C679E4CC55C8055F182413C23505549 + 74DE901C8DB822E7AFC5ECE707709220375DCD1806000054A0B7FBE2CE3DB27A + C7EF97F8597237F65997CE8CFEFDC775E6C938174004008FA83A0B90C7CDCB86 + 1501D522101872728E4EF895430376E77F5865460033030000A54B5BE5AB5DA7 + 4FCB88FEF8BC418F3D6DD0DBEF9AFE16F53DA1E3F7DFD677A3FFB66D7FD7F611 + 744A3C1E9F258280812ACF02E4F176C23C1BC04B307249C65E7BDA74CA710E1D + FD3387A8861FD06E56A0FDCC40FEE7FC6D152FEC0000C09AAD7AEED6DA7DCFDF + CE8FF2F319FDE6CA9DFE3BEF9994137D84ED04C9E63DA1E36F7BBBC1E8FF6131 + FA3F739D8FEDEC93C662B11D4DD39CADD2BA00EBC28100FF0772E466F2B28C51 + A2DD76B669D79D3D3AFE6887B6EDEF92B9190501C1AAB3026A4F760000C09AB4 + EFACDB77FADCD9EB1EE5BE269A3357A7597334FA788E4E4FFEC15CBEA251AB16 + 5D9BC6B3C83C9BCC59FDDC6FF4948EDF7FABA2B31303755AB87061A7FAF6F51A + E3AAB63A6067F17F260702BCBB20FF67F2CC8025223AD723B7A697DB78CC516E + 2FDBF668D7EFBBB47DFFE0BDED30C0F57F0700007A0E4ECCFBF8139DB87ACDB2 + 3CBF93E72E8B3BFCF767EAA2D3F3E8BFB34DCA84DBCB73BF2046FBA2EB27AD27 + 76FAED71E7EF38CE5962F43FB9338F5FEF496E95CB023B838301D1F1936307DF + 05CF3283CF81376F30C269227DB5559301004079BC61AC135E09D0820E9EF1B9 + DF0967763D37F8373ECFFB33C48A66F3AFD7DBEE64E2DF2A1FD5FA89C7E3671A + 863159F584C0F5911FE97B6E5B50E0DF0600809E476B3782D7B5EFEECBDFEEA9 + 23FC35BEDF70EA3F97CBAD33F16FA5DFDB90170B2E0518433CF4920000005285 + 53FF43C5E87FCAFAFCDE06E7B96FB555C2CBE57AEEA5000000809E4ED374725D + 67BDA6FEDB7E77435FB4AE6ECB9D342D32932F052008000000E85E3CF5CF898E + ADADEE7A4DFDB7FDFEC6BC783E1FA0279506020000F474F9EBFE9E97DD79D1A2 + 2FFFB741CFB1B18D1041C06C1104EC584C4981000000AACAAFF6B721D7FD577A + 9EAE684CB04AA03110498100000085C5237F31E89E924C26876ECCF3744900C0 + AB049695E9B39114080000503861C6FF4762E43F70639FABCB56BB4752200000 + 40E16C4CC67F87CFD7958D435220000040D7E36BFEA6E95126B36119FF1D3E67 + 5737124100000040D7C977FE62F4BFD3C2855FCEEAB2E72D446311040000006C + BC4275FEFE7317AAD10802000000361C5FF3B72C9793FEBABCF3F79FBF908DE7 + 20C034CDC9480C040000E89C7675FE5D92EDBFC6D729F41BE9DB77CBEF1986F1 + BF5C8E572CC23A010000006B92EFFCBBA2CE7F9DAFD51D6F88D709106FE8F722 + 1018884B02000000ABCB2FEF6BDBF646ADF0D7E9D7EBCE37974824268B377726 + 8200000080EF14FA7A7F87AFD9DD6F1279010000008176D7FB67893EF1D8AEAA + F1EFD46BCB78C3BC6AA0691A33392F8008B3010000507ABA7BCA7FB5D797F9E6 + 71490000004A4DBB51FF6CD1F71DD39DA3FE95DA21FB83C06C0000009402EEF4 + 99CC51FF4AED91FD81E4C5E3F12986610CC9070008040000A018E43BFEEE2AEF + EB74BB6437A0BD55CB051902010000E88956E9F8278B9BB78BCEFF13D9ED6A6B + 9FEC06740481000000F454ED3B7ED9D7F9D7DA4ED90D581B04020000D013E43B + FDFC6D1547FCABB55976033A2391486C27BE5DAEEBFAD0F601008201000090A9 + 7DC7CFBBF6398E77D6A245A9C9B2DBD5A9B6CB6EC0FAC807029645436DBB7DD3 + B97A4076EB0000A0D8B5EFF0F33F73629FB8799BCAA3FD0EDF8BEC066CA83018 + B822BF8E407B9819000080AEB2864EFF21F1353D9D4E4F93DDBE0D7E5FB21BD0 + 1562B1D829BAAEED6359DA392BCF0C04101000004067ACDAD9E7EFB36DFB03F1 + FD2DD19FDCAD6242DF06BD57D90DE86A757575DBDAB6BB0F917B4924A27DDF71 + F44EFD1E82040080E2D651E7BEA6C7890EFF7DF1FDDFE2EBDD6432395576DB0B + F279C86E40A1F1A502D1B9EF29FE1307799EBB9769D2EE222888CA6E170000C8 + 154EE5BF256E4E175FBAE82B3ED475FDED9E762D7F83DFBFEC06C8D2B76FDFFE + 8EE318E2E6AEE23F9DBFEF4EC11FC060F147618BEF8EF839625934487C4C39D9 + ED050080D5699A6765B3F4AEB8D92ACEDD46FE6E710EB7C4CF6F8BAF8CB86D8A + FBF8EB1DF1B3E8F3DDFF16CB34FEC628D900000000A094210000000028410800 + 0000004A100200000080128400000000A0042100000000284108000000004A10 + 0200000080128400000000A0042100000000284108000000004A100200000080 + 128400000000A0042100000000284108000000004A1002000000801284000000 + 00A0042100000000284108000000004A100200000080128400000000A0042100 + 000000284108000000004A10020000008012F4FF2D1A23A3471D752C00000000 + 49454E44AE426082} + Proportional = True + Stretch = True + end object cmdInstall: TButton - Left = 229 - Top = 200 + Left = 376 + Top = 312 Width = 75 Height = 25 Caption = '&Update now' @@ -184,8 +1275,8 @@ object frmStartInstall: TfrmStartInstall TabOrder = 0 end object cmdLater: TButton - Left = 343 - Top = 200 + Left = 480 + Top = 312 Width = 75 Height = 25 Caption = '&Later' diff --git a/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas index 78578e66c0..bc6722dafb 100644 --- a/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas +++ b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas @@ -24,19 +24,21 @@ type // The 4 valid installation form scenarios plus a None case for validation TInstallCase = ( icNone, // Not a valid case, can be used as check before calling creating form - icRestartRequiredMetered, - icRestartRequiredNotMetered, - icReadyToInstallNotMetered, // Metered warning never needed if ReadyToInstall - icNoInstallMessageMetered + icDownloadRestartMetered, + icDownloadRestart, + icDownload, + icReadyToInstall, // Metered warning never needed if ReadyToInstall + icDownloadMetered ); TfrmStartInstall = class(TfrmKeymanBase) cmdInstall: TButton; cmdLater: TButton; lblUpdateMessage: TLabel; + lblDownloadRequired: TLabel; imgKeymanLogo: TImage; - shpMeteredWarning: TShape; lblMeteredWarning: TLabel; + imgCaution: TImage; procedure FormCreate(Sender: TObject); private FScenario: TInstallCase; @@ -70,35 +72,50 @@ begin cmdLater.Caption := MsgFromId(S_Later); // Default UI configuration state - metered warnings hidden initially - lblUpdateMessage.Visible := True; + lblUpdateMessage.Visible := False; + lblDownloadRequired.Visible := False; lblMeteredWarning.Visible := False; - shpMeteredWarning.Visible := False; + imgCaution.Visible := False; case FScenario of - icRestartRequiredMetered: + icDownloadRestartMetered: begin lblUpdateMessage.Caption := MsgFromId(S_Update_Restart_Req); + lblUpdateMessage.Visible := True; + lblDownloadRequired.Caption := MsgFromId(S_Update_Download_Required); + lblDownloadRequired.Visible := True; lblMeteredWarning.Caption := MsgFromId(S_Metered_Warning); lblMeteredWarning.Visible := True; - shpMeteredWarning.Visible := True; + imgCaution.Visible := True; end; - icRestartRequiredNotMetered: + icDownloadRestart: begin lblUpdateMessage.Caption := MsgFromId(S_Update_Restart_Req); + lblUpdateMessage.Visible := True; + lblDownloadRequired.Caption := MsgFromId(S_Update_Download_Required); + lblDownloadRequired.Visible := True; end; - icReadyToInstallNotMetered: + icDownload: + begin + lblDownloadRequired.Caption := MsgFromId(S_Update_Download_Required); + lblDownloadRequired.Visible := True; + end; + + icReadyToInstall: begin lblUpdateMessage.Caption := MsgFromId(S_Ready_To_Install); + lblUpdateMessage.Visible := True; end; - icNoInstallMessageMetered: + icDownloadMetered: begin - lblUpdateMessage.Visible := False; + lblDownloadRequired.Caption := MsgFromId(S_Update_Download_Required); + lblDownloadRequired.Visible := True; lblMeteredWarning.Caption := MsgFromId(S_Metered_Warning); lblMeteredWarning.Visible := True; - shpMeteredWarning.Visible := True; + imgCaution.Visible := True; end; end; end; diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index 700fe322a4..974734a467 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -102,6 +102,14 @@ type * @returns True if the Keyman is ready to install. *) function ValidateReadyToInstall: Boolean; + (** + * Is a download load of the updates required + * One use case is by the UI to warn the user in foreground manual update + * that the update needs to be downloaded + * + * @returns True if in the UpdateAvailable or Downloading State + *) + function IsDownloadRequired: Boolean; function IsInstallingState: Boolean; function GetAutomaticUpdates: Boolean; @@ -592,6 +600,13 @@ begin Result := (CurrentState is InstallingState); end; +function TUpdateStateMachine.IsDownloadRequired: Boolean; +begin + Result := + (CurrentState is UpdateAvailableState) or + (CurrentState is DownloadingState); +end; + // base implmentation to be overiden procedure TState.HandleInstallPackages; diff --git a/windows/src/desktop/kmshell/main/UfrmMain.pas b/windows/src/desktop/kmshell/main/UfrmMain.pas index 4be5c20525..fe5c5c79df 100644 --- a/windows/src/desktop/kmshell/main/UfrmMain.pas +++ b/windows/src/desktop/kmshell/main/UfrmMain.pas @@ -818,28 +818,45 @@ var ShellPath : string; FResult, InstallNow: Boolean; frmStartInstallNow: TfrmStartInstall; - IsMetered: Boolean; + IsMetered, DownloadRequired, RestartRequired: Boolean; InstallCase: TInstallCase; + BUpdateSM: TUpdateStateMachine; begin InstallNow := True; IsMetered := TNetworkConnection.IsMetered; + RestartRequired := HasKeymanRun; + + BUpdateSM := TUpdateStateMachine.Create(False); + try + DownloadRequired := BUpdateSM.IsDownloadRequired; + finally + BUpdateSM.Free; + end; // If a restart is required (HasKeymanRun == True) // OR it is a Metered connection warn the user and allow // them to cancel their request to Install Now. + // Untill TODO: #13711 is implemented also let the user know + // that the update will be downloaded in the background. + // before the install starts. Once #13711 change the message + // to just be a download is required. // Otherwise start installing with out pop-up warnings. InstallCase := TInstallCase.icNone; - if HasKeymanRun and not IsMetered then + if DownloadRequired and RestartRequired and IsMetered then begin - InstallCase := TInstallCase.icRestartRequiredNotMetered; + InstallCase := TInstallCase.icDownloadRestartMetered; end - else if HasKeymanRun and IsMetered then + else if DownloadRequired and RestartRequired and not IsMetered then begin - InstallCase := TInstallCase.icRestartRequiredMetered; + InstallCase := TInstallCase.icDownloadRestart; end - else if (not HasKeymanRun) and IsMetered then + else if DownloadRequired and not RestartRequired and not IsMetered then begin - InstallCase := TInstallCase.icNoInstallMessageMetered; + InstallCase := TInstallCase.icDownload; + end + else if DownloadRequired and not RestartRequired and IsMetered then + begin + InstallCase := TInstallCase.icDownloadMetered; end; // Render dialog if conditions require it diff --git a/windows/src/desktop/kmshell/main/initprog.pas b/windows/src/desktop/kmshell/main/initprog.pas index bb34fedef9..39c4cc50e8 100644 --- a/windows/src/desktop/kmshell/main/initprog.pas +++ b/windows/src/desktop/kmshell/main/initprog.pas @@ -678,7 +678,7 @@ begin fmHelp, fmShowHelp, fmSettings, fmBoot]) then begin // We are ready to install Metered warning not needed even if on Metered connection - frmStartInstall := TfrmStartInstall.Create(nil, TInstallCase.icReadyToInstallNotMetered); + frmStartInstall := TfrmStartInstall.Create(nil, TInstallCase.icReadyToInstall); try Result := frmStartInstall.ShowModal = mrOk; finally diff --git a/windows/src/desktop/kmshell/xml/caution-icon.png b/windows/src/desktop/kmshell/xml/caution-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..4e7b298b246839d7f241f144bd41e47d842531bf GIT binary patch literal 32615 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 literal 0 HcmV?d00001 diff --git a/windows/src/desktop/kmshell/xml/strings.xml b/windows/src/desktop/kmshell/xml/strings.xml index 318b33f483..16206a832c 100644 --- a/windows/src/desktop/kmshell/xml/strings.xml +++ b/windows/src/desktop/kmshell/xml/strings.xml @@ -608,6 +608,11 @@ Installing the update now will require a restart of your computer before you can use Keyman again. Update now anyway? + + + + The update has not yet been downloaded. Keyman Configuration will close, and it will be downloaded in the background before continuing installation. + From c32f731978ba2a969387e0aee8e5b0f3961d7fee Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Tue, 14 Jul 2026 11:30:02 +1000 Subject: [PATCH 101/140] fix(windows): need restart only case manual update Also made the form change size if no metered connection as it was looking to sparse. --- ...yman.Configuration.UI.UfrmStartInstall.dfm | 9 +++--- ...yman.Configuration.UI.UfrmStartInstall.pas | 28 +++++++++++++++++-- windows/src/desktop/kmshell/main/UfrmMain.pas | 4 +++ 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.dfm b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.dfm index ea0fa43a54..37e9ee2dd6 100644 --- a/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.dfm +++ b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.dfm @@ -20,8 +20,9 @@ object frmStartInstall: TfrmStartInstall object lblUpdateMessage: TLabel Left = 32 Top = 80 - Width = 392 - Height = 17 + Width = 523 + Height = 41 + AutoSize = False Caption = 'An update to Keyman has been downloaded and is ready to install.' Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText @@ -37,7 +38,7 @@ object frmStartInstall: TfrmStartInstall Width = 523 Height = 65 AutoSize = False - Caption = + Caption = 'The update has not yet been downloaded. Keyman Configuration wil' + 'l close, and it will be downloaded in the background before cont' + 'inuing installation.' @@ -171,7 +172,7 @@ object frmStartInstall: TfrmStartInstall Width = 471 Height = 65 AutoSize = False - Caption = + Caption = 'You\'#39're on a metered connection. Downloading now may incur data ' + 'charges' Color = clBtnFace diff --git a/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas index bc6722dafb..29aacb67b3 100644 --- a/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas +++ b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas @@ -21,14 +21,15 @@ uses Vcl.Imaging.pngimage; type - // The 4 valid installation form scenarios plus a None case for validation + // The 6 valid installation form scenarios plus a None case for validation TInstallCase = ( icNone, // Not a valid case, can be used as check before calling creating form icDownloadRestartMetered, icDownloadRestart, icDownload, - icReadyToInstall, // Metered warning never needed if ReadyToInstall - icDownloadMetered + icDownloadMetered, + icRestart, + icReadyToInstall // Metered warning never needed if ReadyToInstall ); TfrmStartInstall = class(TfrmKeymanBase) @@ -42,6 +43,7 @@ type procedure FormCreate(Sender: TObject); private FScenario: TInstallCase; + procedure UpdateLayout; public constructor Create( AOwner: TComponent; @@ -65,6 +67,18 @@ begin inherited Create(AOwner); end; +procedure TfrmStartInstall.UpdateLayout; +const + CompactClientHeight = 260; + ButtonBottomMargin = 13; +begin + if not lblMeteredWarning.Visible then + ClientHeight := CompactClientHeight; + + cmdInstall.Top := ClientHeight - cmdInstall.Height - ButtonBottomMargin; + cmdLater.Top := cmdInstall.Top; +end; + procedure TfrmStartInstall.FormCreate(Sender: TObject); begin inherited; @@ -103,6 +117,12 @@ begin lblDownloadRequired.Visible := True; end; + icRestart: + begin + lblUpdateMessage.Caption := MsgFromId(S_Update_Restart_Req); + lblUpdateMessage.Visible := True; + end; + icReadyToInstall: begin lblUpdateMessage.Caption := MsgFromId(S_Ready_To_Install); @@ -118,6 +138,8 @@ begin imgCaution.Visible := True; end; end; + + UpdateLayout; end; end. diff --git a/windows/src/desktop/kmshell/main/UfrmMain.pas b/windows/src/desktop/kmshell/main/UfrmMain.pas index fe5c5c79df..e367a795ae 100644 --- a/windows/src/desktop/kmshell/main/UfrmMain.pas +++ b/windows/src/desktop/kmshell/main/UfrmMain.pas @@ -857,6 +857,10 @@ begin else if DownloadRequired and not RestartRequired and IsMetered then begin InstallCase := TInstallCase.icDownloadMetered; + end + else if not DownloadRequired and RestartRequired and not IsMetered then + begin + InstallCase := TInstallCase.icRestart; end; // Render dialog if conditions require it From ffd45e139435bb299fa29fc6624290a40e568b07 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Tue, 14 Jul 2026 15:44:29 +1000 Subject: [PATCH 102/140] fix(windows): fix caution image allignment metered connection. --- .../kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.dfm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.dfm b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.dfm index 37e9ee2dd6..d9ee897ce0 100644 --- a/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.dfm +++ b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.dfm @@ -184,12 +184,13 @@ object frmStartInstall: TfrmStartInstall ParentColor = False ParentFont = False Transparent = False + Layout = tlCenter Visible = False WordWrap = True end object imgCaution: TImage Left = 32 - Top = 240 + Top = 245 Width = 38 Height = 38 Picture.Data = { From c9b11c052b023d6aa319f13bfca15f8b24289829 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Tue, 14 Jul 2026 16:16:01 +0200 Subject: [PATCH 103/140] chore(android): patch enable_system_keyboard string --- .../kmapro/GetStartedActivity.java | 22 +++++++++---------- .../kmapro/KeymanSettingsFragment.java | 2 +- .../com/tavultesoft/kmapro/MainActivity.java | 10 ++++----- .../kMAPro/src/main/res/menu/nav_menu.xml | 6 ++--- .../kMAPro/src/main/res/values/strings.xml | 7 ++++-- 5 files changed, 25 insertions(+), 22 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/GetStartedActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/GetStartedActivity.java index 821b042e26..2fc9e90d41 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/GetStartedActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/GetStartedActivity.java @@ -101,7 +101,7 @@ public class GetStartedActivity extends BaseActivity { hashMap = new HashMap(); hashMap.put(iconKey, "0"); - hashMap.put(textKey, getString(R.string.system_keyboard)); + hashMap.put(textKey, getString(R.string.enable_system_keyboard)); hashMap.put(isEnabledKey, "true"); list.add(hashMap); @@ -177,17 +177,17 @@ public class GetStartedActivity extends BaseActivity { super.onWindowFocusChanged(hasFocus); if (hasFocus) { if (openDefaultKeyboardPickerOnly) { - - // When the user taps 'Set Default Keyboard' from the drawer, - // GetStartedActivity is launched in picker-only mode. But you - // can't open the system keyboard picker directly from a menu - // tap — it requires a window with focus. So we use - // onWindowFocusChanged as the trigger point: first focus opens - // the picker, and when the user dismisses it and focus returns - // to this activity, we call finish() to close it. The activity - // is essentially just a invisible bridge to show the system + + // When the user taps 'Set Default Keyboard' from the drawer, + // GetStartedActivity is launched in picker-only mode. But you + // can't open the system keyboard picker directly from a menu + // tap — it requires a window with focus. So we use + // onWindowFocusChanged as the trigger point: first focus opens + // the picker, and when the user dismisses it and focus returns + // to this activity, we call finish() to close it. The activity + // is essentially just a invisible bridge to show the system // picker. - + if (!didAutoOpenDefaultKeyboardPicker) { didAutoOpenDefaultKeyboardPicker = true; InputMethodManager imManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsFragment.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsFragment.java index e15c444887..e1819dd97f 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsFragment.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsFragment.java @@ -75,7 +75,7 @@ public class KeymanSettingsFragment extends PreferenceFragmentCompat { }; setSystemKeyboardPreference = new CheckBoxPreference(context); - setSystemKeyboardPreference.setTitle(R.string.system_keyboard); + setSystemKeyboardPreference.setTitle(R.string.system_keyboards); setSystemKeyboardPreference.setSingleLineTitle(false); setSystemKeyboardPreference.setDefaultValue(SystemIMESettings.isEnabledAsSystemKB(context)); setSystemKeyboardPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index 4c51811a86..cb354d77e1 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -233,7 +233,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene int flags = window.getDecorView().getSystemUiVisibility(); boolean isDarkMode = isDarkMode(); - + if (!isDarkMode) { flags |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; } else { @@ -250,7 +250,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene getSupportActionBar().setDisplayShowHomeEnabled(true); getSupportActionBar().setLogo(R.drawable.keyman_logo); getSupportActionBar().setDisplayShowTitleEnabled(false); - // For Darkmode - once switched to dark mode, the logo becomes white + // For Darkmode - once switched to dark mode, the logo becomes white getSupportActionBar().setLogo(R.drawable.keyman_logo_mode); getSupportActionBar().setBackgroundDrawable(getActionBarDrawable(this)); @@ -573,7 +573,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene toolbarUpdate.setVisible(anUpdateCount > 0); } } - + // 2. Update the Drawer/Sidebar Navigation Menu if (navigationView != null) { final MenuItem drawerUpdate = navigationView.getMenu().findItem(R.id.action_update_keyboards); @@ -857,7 +857,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene public void downloadKMP(String packageId, String bcp47, KmpInstallMode installMode) { Uri downloadUri = bcp47 == null ? - Uri.parse(KMString.format("https://keyman.com/go/package/download/%s", new Object[] { packageId })) : + Uri.parse(KMString.format("https://keyman.com/go/package/download/%s", new Object[] { packageId })) : Uri.parse(KMString.format("https://keyman.com/go/package/download/%s?bcp47=%s", new Object[] { packageId, bcp47 })); downloadKMP(downloadUri, installMode); } @@ -1316,7 +1316,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } private boolean handleDrawerNavigationItemSelected(int id) { - if (id == R.id.system_keyboard) { + if (id == R.id.system_keyboards) { startActivity(new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS)); } else if (id == R.id.action_update_keyboards) { KMManager.getUpdateTool().executeOpenUpdates(); diff --git a/android/KMAPro/kMAPro/src/main/res/menu/nav_menu.xml b/android/KMAPro/kMAPro/src/main/res/menu/nav_menu.xml index a2589abff7..2929370f57 100644 --- a/android/KMAPro/kMAPro/src/main/res/menu/nav_menu.xml +++ b/android/KMAPro/kMAPro/src/main/res/menu/nav_menu.xml @@ -3,7 +3,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity"> - + @@ -78,9 +78,9 @@ android:title="@string/theme" /> diff --git a/android/KMAPro/kMAPro/src/main/res/values/strings.xml b/android/KMAPro/kMAPro/src/main/res/values/strings.xml index e5892b084a..3c357b6154 100644 --- a/android/KMAPro/kMAPro/src/main/res/values/strings.xml +++ b/android/KMAPro/kMAPro/src/main/res/values/strings.xml @@ -99,7 +99,10 @@ Add a keyboard for your language - System keyboards + System keyboards + + + Enable Keyman as system-wide keyboard Set Keyman as default keyboard @@ -314,7 +317,7 @@ Unable to launch web browser - + Menu From d9717d634e6b2fc7c37e013b40eb922ccaec7adb Mon Sep 17 00:00:00 2001 From: Mengchou97 Date: Wed, 15 Jul 2026 23:13:28 +0700 Subject: [PATCH 104/140] updated sidebar title and move button and add a get started action button --- .../com/tavultesoft/kmapro/MainActivity.java | 16 +++-------- .../src/main/res/drawable/ic_rocket.png | Bin 0 -> 18933 bytes .../kMAPro/src/main/res/menu/nav_menu.xml | 25 +++++++++--------- .../kMAPro/src/main/res/values/strings.xml | 2 +- 4 files changed, 17 insertions(+), 26 deletions(-) create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable/ic_rocket.png diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index cb354d77e1..2a7c01246e 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -267,8 +267,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene @Override public boolean onNavigationItemSelected(@NonNull MenuItem item) { int id = item.getItemId(); - if (id == R.id.nav_toggle_show_osk || id == R.id.action_get_started - || id == R.id.nav_toggle_send_crash_report) { + if (id == R.id.nav_toggle_show_osk || id == R.id.nav_toggle_send_crash_report) { toggleDrawerSwitch(navigationView, id); return true; } @@ -1331,6 +1330,8 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene startActivity(new Intent(context, KeymanSettingsInstallActivity.class)); } else if (id == R.id.nav_display_language) { startActivity(new Intent(context, KeymanSettingsLocalizeActivity.class)); + } else if (id == R.id.action_get_started) { + startActivity(new Intent(context, GetStartedActivity.class)); } else if (id == R.id.nav_keyboard_height) { startActivity(new Intent(context, AdjustKeyboardHeightActivity.class)); } else if (id == R.id.nav_longpress_delay) { @@ -1396,17 +1397,6 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } }); - bindDrawerSwitch(navigationView, R.id.action_get_started, - prefs.getBoolean(GetStartedActivity.showGetStartedKey, true), - new SwitchChangeHandler() { - @Override - public void onChanged(boolean isChecked) { - SharedPreferences.Editor editor = prefs.edit(); - editor.putBoolean(GetStartedActivity.showGetStartedKey, isChecked); - editor.apply(); - } - }); - bindDrawerSwitch(navigationView, R.id.nav_toggle_send_crash_report, prefs.getBoolean(KeymanSettingsKeys.SEND_CRASH_REPORT, true), new SwitchChangeHandler() { diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_rocket.png b/android/KMAPro/kMAPro/src/main/res/drawable/ic_rocket.png new file mode 100644 index 0000000000000000000000000000000000000000..695eaac970695005b5d76c837735955cafcb6a38 GIT binary patch literal 18933 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 literal 0 HcmV?d00001 diff --git a/android/KMAPro/kMAPro/src/main/res/menu/nav_menu.xml b/android/KMAPro/kMAPro/src/main/res/menu/nav_menu.xml index 2929370f57..8c7c957f99 100644 --- a/android/KMAPro/kMAPro/src/main/res/menu/nav_menu.xml +++ b/android/KMAPro/kMAPro/src/main/res/menu/nav_menu.xml @@ -4,7 +4,7 @@ xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity"> - + + + + + - - - diff --git a/android/KMAPro/kMAPro/src/main/res/values/strings.xml b/android/KMAPro/kMAPro/src/main/res/values/strings.xml index 3c357b6154..4e16f9f7ef 100644 --- a/android/KMAPro/kMAPro/src/main/res/values/strings.xml +++ b/android/KMAPro/kMAPro/src/main/res/values/strings.xml @@ -14,7 +14,7 @@ Web Browser - Setup + Keyboards and Languages Preferences Help From f027c297fd5387c73e891541cb3fd8cf68e47f6b Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Thu, 16 Jul 2026 15:58:09 +1000 Subject: [PATCH 105/140] feat(windows): send errors to sentry --- ...an.Configuration.Util.NetworkConnection.pas | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/windows/src/desktop/kmshell/util/Keyman.Configuration.Util.NetworkConnection.pas b/windows/src/desktop/kmshell/util/Keyman.Configuration.Util.NetworkConnection.pas index ef9824fa48..42106f0d66 100644 --- a/windows/src/desktop/kmshell/util/Keyman.Configuration.Util.NetworkConnection.pas +++ b/windows/src/desktop/kmshell/util/Keyman.Configuration.Util.NetworkConnection.pas @@ -4,7 +4,11 @@ * Notes: Enable checking for metered connection and background data restrictions. *) unit Keyman.Configuration.Util.NetworkConnection; - +(** + * Winapi.Networking.Connectivity uses TWinRTImportHelper this means + * ROInitialize does not need to be called when using this unit + * as it is already managed. + *) interface @@ -41,9 +45,9 @@ type implementation uses + Sentry.Client, System.SysUtils, Winapi.CommonTypes, - Winapi.WinRT, Winapi.Networking.Connectivity; class function TNetworkConnection.IsMetered: Boolean; @@ -68,7 +72,10 @@ begin // If the WinRT network APIs are unavailable or throw (e.g. unusual // Windows SKUs, containers, Network List Manager service stopped), // treat the connection as non-metered so updates are not blocked. - Result := False; + begin + Result := False; + SentryHandleException(E); + end; end; end; @@ -90,7 +97,10 @@ begin except on E: Exception do // See IsMetered: default to not-restricted if the WinRT APIs throw. - Result := False; + begin + Result := False; + SentryHandleException(E); + end; end; end; From 363f71e9de77344debbc0b81473fdf4edd3b3acc Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Fri, 31 Jul 2026 18:49:07 +0200 Subject: [PATCH 106/140] refactor(web): improve naming of `_SetTargetDir` function `_SetTargetDir` deals with setting the text direction of the target, not the target directory. This change mainly renames the private function to `setTargetTextDirection`. Build-bot: skip build:web Test-bot: skip --- web/src/app/browser/src/contextManager.ts | 26 ++++++++++++----------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/web/src/app/browser/src/contextManager.ts b/web/src/app/browser/src/contextManager.ts index 0bea973161..396b08fbf3 100644 --- a/web/src/app/browser/src/contextManager.ts +++ b/web/src/app/browser/src/contextManager.ts @@ -24,21 +24,23 @@ export interface KeyboardCookie { * has the same directionality, text runs will be re-ordered which is confusing and causes * incorrect caret positioning * - * @param {Object} Ptarg Target element + * @param {Object} elem Target element * @param {Keyboard} activeKeyboard The active keyboard */ -function _SetTargDir(Ptarg: HTMLElement, activeKeyboard: Keyboard) { +function setTargetTextDirection(elem: HTMLElement, activeKeyboard: Keyboard): void { + if (!elem) { + return; + } + const elDir = activeKeyboard?.isRTL ? 'rtl' : 'ltr'; - if(Ptarg) { - if(Ptarg instanceof Ptarg.ownerDocument.defaultView.HTMLInputElement - || Ptarg instanceof Ptarg.ownerDocument.defaultView.HTMLTextAreaElement) { - if((Ptarg as HTMLInputElement|HTMLTextAreaElement).value.length == 0) { - Ptarg.dir=elDir; - } - } else if(typeof Ptarg.textContent == "string" && Ptarg.textContent.length == 0) { // As with contenteditable DIVs, for example. - Ptarg.dir=elDir; + if (elem instanceof elem.ownerDocument.defaultView.HTMLInputElement + || elem instanceof elem.ownerDocument.defaultView.HTMLTextAreaElement) { + if ((elem as HTMLInputElement|HTMLTextAreaElement).value.length == 0) { + elem.dir=elDir; } + } else if (typeof elem.textContent == "string" && elem.textContent.length == 0) { // As with contenteditable DIVs, for example. + elem.dir=elDir; } } @@ -294,7 +296,7 @@ export class ContextManager extends ContextManagerBase { focusedElement = textStore.docRoot; } if(focusedElement && focusedElement.ownerDocument && focusedElement instanceof focusedElement.ownerDocument.defaultView.HTMLElement) { - _SetTargDir(focusedElement, this.activeKeyboard?.keyboard); + setTargetTextDirection(focusedElement, this.activeKeyboard?.keyboard); } if(textStore != originalTextStore) { @@ -510,7 +512,7 @@ export class ContextManager extends ContextManagerBase { // Only do these if the active keyboard-textstore still matches the original keyboard-textStore; // otherwise, maintain what's correct for the currently active one. if(originalKeyboardTextStore == this.currentKeyboardSrcTextStore()) { - _SetTargDir(this.currentTextStore?.getElement(), this.keyboardCache.getKeyboard(keyboardId)); + setTargetTextDirection(this.currentTextStore?.getElement(), this.keyboardCache.getKeyboard(keyboardId)); this.page.setAttachmentFont(this.activeKeyboard?.metadata?.KFont, this.engineConfig.paths.fonts, this.engineConfig.hostDevice.OS); this.restoreLastActiveTextStore(); From 9854206f6a0a080967e6274e760b00fa8e60504c Mon Sep 17 00:00:00 2001 From: Sabine Date: Fri, 7 Aug 2026 11:59:42 +0200 Subject: [PATCH 107/140] chore(common): use shorter input paths for km_vkey.h and #include also in windows/src/engine/... --- core/src/kmx/kmx_base.h | 2 +- developer/src/kmanalyze/kmanalyze.cpp | 2 +- developer/src/kmcmplib/src/CharToKeyConversion.cpp | 2 +- developer/src/kmcmplib/src/Compiler.cpp | 2 +- developer/src/kmdecomp/savekeyboard.cpp | 2 +- linux/mcompile/keymap/keymap.h | 2 +- mac/Keyman4Mac/Keyman4Mac/WindowsVKCodes.h | 3 ++- mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/WindowsVKCodes.h | 4 ++-- mac/mcompile/keymap.h | 4 +--- windows/src/engine/keyman32/syskbd.h | 7 +++++-- windows/src/engine/mcompile/mc_syskbd.h | 6 ++++-- 11 files changed, 20 insertions(+), 16 deletions(-) diff --git a/core/src/kmx/kmx_base.h b/core/src/kmx/kmx_base.h index c2c6fb5722..30b9ab5c75 100644 --- a/core/src/kmx/kmx_base.h +++ b/core/src/kmx/kmx_base.h @@ -1,7 +1,7 @@ #pragma once #include "keyman_core.h" -#include "../../../common/include/km_vkey.h" +#include "km_vkey.h" #if defined(_WIN32) || defined(_WIN64) #define snprintf _snprintf diff --git a/developer/src/kmanalyze/kmanalyze.cpp b/developer/src/kmanalyze/kmanalyze.cpp index ed8e7c7d48..7ce83b7d6d 100644 --- a/developer/src/kmanalyze/kmanalyze.cpp +++ b/developer/src/kmanalyze/kmanalyze.cpp @@ -7,7 +7,7 @@ #include #include #include -#include "../../../common/include/km_vkey.h" +#include "km_vkey.h" using namespace km::vkey; diff --git a/developer/src/kmcmplib/src/CharToKeyConversion.cpp b/developer/src/kmcmplib/src/CharToKeyConversion.cpp index 5aaed7d409..db87fe5dbd 100644 --- a/developer/src/kmcmplib/src/CharToKeyConversion.cpp +++ b/developer/src/kmcmplib/src/CharToKeyConversion.cpp @@ -1,7 +1,7 @@ #include "pch.h" #include "CharToKeyConversion.h" -#include "../../../../common/include/km_vkey.h" +#include "km_vkey.h" /* Following VKeys come from Windows */ diff --git a/developer/src/kmcmplib/src/Compiler.cpp b/developer/src/kmcmplib/src/Compiler.cpp index 2d3cf2b179..32f9353411 100644 --- a/developer/src/kmcmplib/src/Compiler.cpp +++ b/developer/src/kmcmplib/src/Compiler.cpp @@ -77,7 +77,7 @@ #include "cp1252.h" #include "virtualcharkeys.h" -#include "../../../../common/include/km_vkey.h" +#include "km_vkey.h" #include "../../../../common/include/vkeys.h" #include "../../../../common/include/keymanversion.h" diff --git a/developer/src/kmdecomp/savekeyboard.cpp b/developer/src/kmdecomp/savekeyboard.cpp index 2b70b7ffbb..33935ab8b2 100644 --- a/developer/src/kmdecomp/savekeyboard.cpp +++ b/developer/src/kmdecomp/savekeyboard.cpp @@ -33,7 +33,7 @@ #include "../../../common/windows/cpp/include/legacy_kmx_file.h" #include "../../../common/windows/cpp/include/legacy_kmx_memory.h" #include "../../../common/include/vkeys.h" -#include "../../../common/include/km_vkey.h" +#include "km_vkey.h" using namespace km::vkey; #define SSN__PREFIX L"&" diff --git a/linux/mcompile/keymap/keymap.h b/linux/mcompile/keymap/keymap.h index ac280ea7b5..bd3e37aef8 100644 --- a/linux/mcompile/keymap/keymap.h +++ b/linux/mcompile/keymap/keymap.h @@ -15,7 +15,7 @@ #include #include "km_u16.h" #include -#include "../../../common/include/km_vkey.h" +#include "km_vkey.h" enum ShiftState { diff --git a/mac/Keyman4Mac/Keyman4Mac/WindowsVKCodes.h b/mac/Keyman4Mac/Keyman4Mac/WindowsVKCodes.h index b7a15b2ca9..3c911bdefd 100644 --- a/mac/Keyman4Mac/Keyman4Mac/WindowsVKCodes.h +++ b/mac/Keyman4Mac/Keyman4Mac/WindowsVKCodes.h @@ -6,7 +6,7 @@ // Copyright (c) 2017 SIL International. All rights reserved. // -#define VK_BACKSPACE 0x08 +/*#define VK_BACKSPACE 0x08 #define VK_TAB 0x09 #define VK_ENTER 0x0D #define VK_SPACE 0x20 @@ -77,3 +77,4 @@ #define VK_BACKSLASH 0xDC #define VK_RIGHT_BRACKET 0xDD #define VK_QUOTE 0xDE +*/ diff --git a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/WindowsVKCodes.h b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/WindowsVKCodes.h index 2fc5ba7f79..83156d10fd 100644 --- a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/WindowsVKCodes.h +++ b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/WindowsVKCodes.h @@ -6,7 +6,7 @@ // Copyright (c) 2017 SIL International. All rights reserved. // -#ifndef WindowsVKCodes_h +/*#ifndef WindowsVKCodes_h #define WindowsVKCodes_h #define VK_BACKSPACE 0x08 @@ -83,4 +83,4 @@ #define VK_OEM_102 0xE2 -#endif /* WindowsVKCodes_h */ +#endif*/ /* WindowsVKCodes_h */ diff --git a/mac/mcompile/keymap.h b/mac/mcompile/keymap.h index 9aabce0fea..9dfca2b5ff 100644 --- a/mac/mcompile/keymap.h +++ b/mac/mcompile/keymap.h @@ -8,9 +8,7 @@ #include #include "../../common/include/km_u16.h" - -#include "../../common/include/km_vkey.h" -//#include "../../../../common/include/km_vkey.h" +#include "km_vkey.h" enum ShiftState { Base = 0, // 0 Shft = 1, // 1 diff --git a/windows/src/engine/keyman32/syskbd.h b/windows/src/engine/keyman32/syskbd.h index 23ef2d752a..985a5637c6 100644 --- a/windows/src/engine/keyman32/syskbd.h +++ b/windows/src/engine/keyman32/syskbd.h @@ -22,7 +22,10 @@ #ifndef __SYSKBD_H #define __SYSKBD_H -#define VK_COLON 0xBA +#include "km_vkey.h" + + +/*#define VK_COLON 0xBA #define VK_EQUAL 0xBB #define VK_COMMA 0xBC #define VK_HYPHEN 0xBD @@ -33,7 +36,7 @@ #define VK_BKSLASH 0xDC #define VK_RBRKT 0xDD #define VK_QUOTE 0xDE -#define VK_xDF 0xDF +#define VK_xDF 0xDF*/ WCHAR CharFromVK(WORD *VKey, UINT ShiftFlags); // I4582 WORD USVKToScanCodeToLayoutVK(WORD VKey); // I3762 diff --git a/windows/src/engine/mcompile/mc_syskbd.h b/windows/src/engine/mcompile/mc_syskbd.h index 11ad043933..78b06b0dfe 100644 --- a/windows/src/engine/mcompile/mc_syskbd.h +++ b/windows/src/engine/mcompile/mc_syskbd.h @@ -2,7 +2,9 @@ #ifndef _SYSKBD_H #define _SYSKBD_H -#define VK_COLON 0xBA +#include "km_vkey.h" + +/*#define VK_COLON 0xBA #define VK_EQUAL 0xBB #define VK_COMMA 0xBC #define VK_HYPHEN 0xBD @@ -13,7 +15,7 @@ #define VK_BKSLASH 0xDC #define VK_RBRKT 0xDD #define VK_QUOTE 0xDE -#define VK_xDF 0xDF +#define VK_xDF 0xDF*/ BOOL LoadNewLibrary(PWSTR filename); WCHAR CharFromVK(WORD VKey, UINT ShiftFlags, WCHAR *PDeadKey); From dde35c87cc0c4c3fe9c438d3d3ce5128af4d2229 Mon Sep 17 00:00:00 2001 From: Sabine Date: Fri, 7 Aug 2026 12:55:06 +0200 Subject: [PATCH 108/140] chore(common): undo last changes --- core/src/kmx/kmx_base.h | 2 +- developer/src/kmanalyze/kmanalyze.cpp | 2 +- developer/src/kmcmplib/src/CharToKeyConversion.cpp | 2 +- developer/src/kmcmplib/src/Compiler.cpp | 2 +- developer/src/kmdecomp/savekeyboard.cpp | 2 +- linux/mcompile/keymap/keymap.h | 2 +- mac/Keyman4Mac/Keyman4Mac/WindowsVKCodes.h | 3 +-- mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/WindowsVKCodes.h | 4 ++-- mac/mcompile/keymap.h | 4 +++- windows/src/engine/keyman32/syskbd.h | 7 ++----- windows/src/engine/mcompile/mc_syskbd.h | 6 ++---- 11 files changed, 16 insertions(+), 20 deletions(-) diff --git a/core/src/kmx/kmx_base.h b/core/src/kmx/kmx_base.h index 30b9ab5c75..c2c6fb5722 100644 --- a/core/src/kmx/kmx_base.h +++ b/core/src/kmx/kmx_base.h @@ -1,7 +1,7 @@ #pragma once #include "keyman_core.h" -#include "km_vkey.h" +#include "../../../common/include/km_vkey.h" #if defined(_WIN32) || defined(_WIN64) #define snprintf _snprintf diff --git a/developer/src/kmanalyze/kmanalyze.cpp b/developer/src/kmanalyze/kmanalyze.cpp index 7ce83b7d6d..ed8e7c7d48 100644 --- a/developer/src/kmanalyze/kmanalyze.cpp +++ b/developer/src/kmanalyze/kmanalyze.cpp @@ -7,7 +7,7 @@ #include #include #include -#include "km_vkey.h" +#include "../../../common/include/km_vkey.h" using namespace km::vkey; diff --git a/developer/src/kmcmplib/src/CharToKeyConversion.cpp b/developer/src/kmcmplib/src/CharToKeyConversion.cpp index db87fe5dbd..5aaed7d409 100644 --- a/developer/src/kmcmplib/src/CharToKeyConversion.cpp +++ b/developer/src/kmcmplib/src/CharToKeyConversion.cpp @@ -1,7 +1,7 @@ #include "pch.h" #include "CharToKeyConversion.h" -#include "km_vkey.h" +#include "../../../../common/include/km_vkey.h" /* Following VKeys come from Windows */ diff --git a/developer/src/kmcmplib/src/Compiler.cpp b/developer/src/kmcmplib/src/Compiler.cpp index 32f9353411..2d3cf2b179 100644 --- a/developer/src/kmcmplib/src/Compiler.cpp +++ b/developer/src/kmcmplib/src/Compiler.cpp @@ -77,7 +77,7 @@ #include "cp1252.h" #include "virtualcharkeys.h" -#include "km_vkey.h" +#include "../../../../common/include/km_vkey.h" #include "../../../../common/include/vkeys.h" #include "../../../../common/include/keymanversion.h" diff --git a/developer/src/kmdecomp/savekeyboard.cpp b/developer/src/kmdecomp/savekeyboard.cpp index 33935ab8b2..2b70b7ffbb 100644 --- a/developer/src/kmdecomp/savekeyboard.cpp +++ b/developer/src/kmdecomp/savekeyboard.cpp @@ -33,7 +33,7 @@ #include "../../../common/windows/cpp/include/legacy_kmx_file.h" #include "../../../common/windows/cpp/include/legacy_kmx_memory.h" #include "../../../common/include/vkeys.h" -#include "km_vkey.h" +#include "../../../common/include/km_vkey.h" using namespace km::vkey; #define SSN__PREFIX L"&" diff --git a/linux/mcompile/keymap/keymap.h b/linux/mcompile/keymap/keymap.h index bd3e37aef8..ac280ea7b5 100644 --- a/linux/mcompile/keymap/keymap.h +++ b/linux/mcompile/keymap/keymap.h @@ -15,7 +15,7 @@ #include #include "km_u16.h" #include -#include "km_vkey.h" +#include "../../../common/include/km_vkey.h" enum ShiftState { diff --git a/mac/Keyman4Mac/Keyman4Mac/WindowsVKCodes.h b/mac/Keyman4Mac/Keyman4Mac/WindowsVKCodes.h index 3c911bdefd..b7a15b2ca9 100644 --- a/mac/Keyman4Mac/Keyman4Mac/WindowsVKCodes.h +++ b/mac/Keyman4Mac/Keyman4Mac/WindowsVKCodes.h @@ -6,7 +6,7 @@ // Copyright (c) 2017 SIL International. All rights reserved. // -/*#define VK_BACKSPACE 0x08 +#define VK_BACKSPACE 0x08 #define VK_TAB 0x09 #define VK_ENTER 0x0D #define VK_SPACE 0x20 @@ -77,4 +77,3 @@ #define VK_BACKSLASH 0xDC #define VK_RIGHT_BRACKET 0xDD #define VK_QUOTE 0xDE -*/ diff --git a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/WindowsVKCodes.h b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/WindowsVKCodes.h index 83156d10fd..2fc5ba7f79 100644 --- a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/WindowsVKCodes.h +++ b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/WindowsVKCodes.h @@ -6,7 +6,7 @@ // Copyright (c) 2017 SIL International. All rights reserved. // -/*#ifndef WindowsVKCodes_h +#ifndef WindowsVKCodes_h #define WindowsVKCodes_h #define VK_BACKSPACE 0x08 @@ -83,4 +83,4 @@ #define VK_OEM_102 0xE2 -#endif*/ /* WindowsVKCodes_h */ +#endif /* WindowsVKCodes_h */ diff --git a/mac/mcompile/keymap.h b/mac/mcompile/keymap.h index 9dfca2b5ff..9aabce0fea 100644 --- a/mac/mcompile/keymap.h +++ b/mac/mcompile/keymap.h @@ -8,7 +8,9 @@ #include #include "../../common/include/km_u16.h" -#include "km_vkey.h" + +#include "../../common/include/km_vkey.h" +//#include "../../../../common/include/km_vkey.h" enum ShiftState { Base = 0, // 0 Shft = 1, // 1 diff --git a/windows/src/engine/keyman32/syskbd.h b/windows/src/engine/keyman32/syskbd.h index 985a5637c6..23ef2d752a 100644 --- a/windows/src/engine/keyman32/syskbd.h +++ b/windows/src/engine/keyman32/syskbd.h @@ -22,10 +22,7 @@ #ifndef __SYSKBD_H #define __SYSKBD_H -#include "km_vkey.h" - - -/*#define VK_COLON 0xBA +#define VK_COLON 0xBA #define VK_EQUAL 0xBB #define VK_COMMA 0xBC #define VK_HYPHEN 0xBD @@ -36,7 +33,7 @@ #define VK_BKSLASH 0xDC #define VK_RBRKT 0xDD #define VK_QUOTE 0xDE -#define VK_xDF 0xDF*/ +#define VK_xDF 0xDF WCHAR CharFromVK(WORD *VKey, UINT ShiftFlags); // I4582 WORD USVKToScanCodeToLayoutVK(WORD VKey); // I3762 diff --git a/windows/src/engine/mcompile/mc_syskbd.h b/windows/src/engine/mcompile/mc_syskbd.h index 78b06b0dfe..11ad043933 100644 --- a/windows/src/engine/mcompile/mc_syskbd.h +++ b/windows/src/engine/mcompile/mc_syskbd.h @@ -2,9 +2,7 @@ #ifndef _SYSKBD_H #define _SYSKBD_H -#include "km_vkey.h" - -/*#define VK_COLON 0xBA +#define VK_COLON 0xBA #define VK_EQUAL 0xBB #define VK_COMMA 0xBC #define VK_HYPHEN 0xBD @@ -15,7 +13,7 @@ #define VK_BKSLASH 0xDC #define VK_RBRKT 0xDD #define VK_QUOTE 0xDE -#define VK_xDF 0xDF*/ +#define VK_xDF 0xDF BOOL LoadNewLibrary(PWSTR filename); WCHAR CharFromVK(WORD VKey, UINT ShiftFlags, WCHAR *PDeadKey); From dfb274a51b5dd71adb5c7bff1403b73ec7cbf5bd Mon Sep 17 00:00:00 2001 From: Sabine Date: Fri, 7 Aug 2026 13:24:26 +0200 Subject: [PATCH 109/140] chore(common): use shorter input paths --- linux/mcompile/keymap/keymap.h | 2 +- mac/mcompile/keymap.h | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/linux/mcompile/keymap/keymap.h b/linux/mcompile/keymap/keymap.h index ac280ea7b5..bd3e37aef8 100644 --- a/linux/mcompile/keymap/keymap.h +++ b/linux/mcompile/keymap/keymap.h @@ -15,7 +15,7 @@ #include #include "km_u16.h" #include -#include "../../../common/include/km_vkey.h" +#include "km_vkey.h" enum ShiftState { diff --git a/mac/mcompile/keymap.h b/mac/mcompile/keymap.h index 9aabce0fea..d3e9595b19 100644 --- a/mac/mcompile/keymap.h +++ b/mac/mcompile/keymap.h @@ -8,9 +8,8 @@ #include #include "../../common/include/km_u16.h" +#include "km_vkey.h" -#include "../../common/include/km_vkey.h" -//#include "../../../../common/include/km_vkey.h" enum ShiftState { Base = 0, // 0 Shft = 1, // 1 From e69204c6a24a14204a4d0c4cf19f82851641e0af Mon Sep 17 00:00:00 2001 From: Sabine Date: Fri, 7 Aug 2026 13:24:26 +0200 Subject: [PATCH 110/140] chore(common): use shorter input paths --- developer/src/kmanalyze/kmanalyze.cpp | 2 +- developer/src/kmdecomp/savekeyboard.cpp | 2 +- linux/mcompile/keymap/keymap.h | 2 +- mac/mcompile/keymap.h | 3 +-- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/developer/src/kmanalyze/kmanalyze.cpp b/developer/src/kmanalyze/kmanalyze.cpp index ed8e7c7d48..7ce83b7d6d 100644 --- a/developer/src/kmanalyze/kmanalyze.cpp +++ b/developer/src/kmanalyze/kmanalyze.cpp @@ -7,7 +7,7 @@ #include #include #include -#include "../../../common/include/km_vkey.h" +#include "km_vkey.h" using namespace km::vkey; diff --git a/developer/src/kmdecomp/savekeyboard.cpp b/developer/src/kmdecomp/savekeyboard.cpp index 2b70b7ffbb..33935ab8b2 100644 --- a/developer/src/kmdecomp/savekeyboard.cpp +++ b/developer/src/kmdecomp/savekeyboard.cpp @@ -33,7 +33,7 @@ #include "../../../common/windows/cpp/include/legacy_kmx_file.h" #include "../../../common/windows/cpp/include/legacy_kmx_memory.h" #include "../../../common/include/vkeys.h" -#include "../../../common/include/km_vkey.h" +#include "km_vkey.h" using namespace km::vkey; #define SSN__PREFIX L"&" diff --git a/linux/mcompile/keymap/keymap.h b/linux/mcompile/keymap/keymap.h index ac280ea7b5..bd3e37aef8 100644 --- a/linux/mcompile/keymap/keymap.h +++ b/linux/mcompile/keymap/keymap.h @@ -15,7 +15,7 @@ #include #include "km_u16.h" #include -#include "../../../common/include/km_vkey.h" +#include "km_vkey.h" enum ShiftState { diff --git a/mac/mcompile/keymap.h b/mac/mcompile/keymap.h index 9aabce0fea..d3e9595b19 100644 --- a/mac/mcompile/keymap.h +++ b/mac/mcompile/keymap.h @@ -8,9 +8,8 @@ #include #include "../../common/include/km_u16.h" +#include "km_vkey.h" -#include "../../common/include/km_vkey.h" -//#include "../../../../common/include/km_vkey.h" enum ShiftState { Base = 0, // 0 Shft = 1, // 1 From 279820b1faa34eca5fcd7a6958fb7bf8ea0f778f Mon Sep 17 00:00:00 2001 From: Sabine Date: Fri, 7 Aug 2026 14:10:58 +0200 Subject: [PATCH 111/140] chore(common): include km_vkey.h --- developer/src/kmanalyze/kmanalyze.cpp | 2 +- windows/src/engine/keyman32/syskbd.h | 7 +++++-- windows/src/engine/mcompile/mc_syskbd.h | 6 ++++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/developer/src/kmanalyze/kmanalyze.cpp b/developer/src/kmanalyze/kmanalyze.cpp index 7ce83b7d6d..ed8e7c7d48 100644 --- a/developer/src/kmanalyze/kmanalyze.cpp +++ b/developer/src/kmanalyze/kmanalyze.cpp @@ -7,7 +7,7 @@ #include #include #include -#include "km_vkey.h" +#include "../../../common/include/km_vkey.h" using namespace km::vkey; diff --git a/windows/src/engine/keyman32/syskbd.h b/windows/src/engine/keyman32/syskbd.h index 23ef2d752a..18d83fc2ae 100644 --- a/windows/src/engine/keyman32/syskbd.h +++ b/windows/src/engine/keyman32/syskbd.h @@ -22,7 +22,10 @@ #ifndef __SYSKBD_H #define __SYSKBD_H -#define VK_COLON 0xBA +#include "../../../../common/include/km_vkey.h" + + +/*#define VK_COLON 0xBA #define VK_EQUAL 0xBB #define VK_COMMA 0xBC #define VK_HYPHEN 0xBD @@ -33,7 +36,7 @@ #define VK_BKSLASH 0xDC #define VK_RBRKT 0xDD #define VK_QUOTE 0xDE -#define VK_xDF 0xDF +#define VK_xDF 0xDF*/ WCHAR CharFromVK(WORD *VKey, UINT ShiftFlags); // I4582 WORD USVKToScanCodeToLayoutVK(WORD VKey); // I3762 diff --git a/windows/src/engine/mcompile/mc_syskbd.h b/windows/src/engine/mcompile/mc_syskbd.h index 11ad043933..fbbdf8d611 100644 --- a/windows/src/engine/mcompile/mc_syskbd.h +++ b/windows/src/engine/mcompile/mc_syskbd.h @@ -1,8 +1,10 @@ +#include "../../../../common/include/km_vkey.h" + #ifndef _SYSKBD_H #define _SYSKBD_H -#define VK_COLON 0xBA +/*#define VK_COLON 0xBA #define VK_EQUAL 0xBB #define VK_COMMA 0xBC #define VK_HYPHEN 0xBD @@ -13,7 +15,7 @@ #define VK_BKSLASH 0xDC #define VK_RBRKT 0xDD #define VK_QUOTE 0xDE -#define VK_xDF 0xDF +#define VK_xDF 0xDF*/ BOOL LoadNewLibrary(PWSTR filename); WCHAR CharFromVK(WORD VKey, UINT ShiftFlags, WCHAR *PDeadKey); From 0b7af5b7713b7075100f70df19f28c9d5ec43896 Mon Sep 17 00:00:00 2001 From: Sabine Date: Fri, 7 Aug 2026 14:10:58 +0200 Subject: [PATCH 112/140] chore(common): include km_vkey.h --- developer/src/kmanalyze/kmanalyze.cpp | 2 +- developer/src/kmdecomp/savekeyboard.cpp | 2 +- windows/src/engine/keyman32/syskbd.h | 7 +++++-- windows/src/engine/mcompile/mc_syskbd.h | 6 ++++-- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/developer/src/kmanalyze/kmanalyze.cpp b/developer/src/kmanalyze/kmanalyze.cpp index 7ce83b7d6d..ed8e7c7d48 100644 --- a/developer/src/kmanalyze/kmanalyze.cpp +++ b/developer/src/kmanalyze/kmanalyze.cpp @@ -7,7 +7,7 @@ #include #include #include -#include "km_vkey.h" +#include "../../../common/include/km_vkey.h" using namespace km::vkey; diff --git a/developer/src/kmdecomp/savekeyboard.cpp b/developer/src/kmdecomp/savekeyboard.cpp index 33935ab8b2..2b70b7ffbb 100644 --- a/developer/src/kmdecomp/savekeyboard.cpp +++ b/developer/src/kmdecomp/savekeyboard.cpp @@ -33,7 +33,7 @@ #include "../../../common/windows/cpp/include/legacy_kmx_file.h" #include "../../../common/windows/cpp/include/legacy_kmx_memory.h" #include "../../../common/include/vkeys.h" -#include "km_vkey.h" +#include "../../../common/include/km_vkey.h" using namespace km::vkey; #define SSN__PREFIX L"&" diff --git a/windows/src/engine/keyman32/syskbd.h b/windows/src/engine/keyman32/syskbd.h index 23ef2d752a..18d83fc2ae 100644 --- a/windows/src/engine/keyman32/syskbd.h +++ b/windows/src/engine/keyman32/syskbd.h @@ -22,7 +22,10 @@ #ifndef __SYSKBD_H #define __SYSKBD_H -#define VK_COLON 0xBA +#include "../../../../common/include/km_vkey.h" + + +/*#define VK_COLON 0xBA #define VK_EQUAL 0xBB #define VK_COMMA 0xBC #define VK_HYPHEN 0xBD @@ -33,7 +36,7 @@ #define VK_BKSLASH 0xDC #define VK_RBRKT 0xDD #define VK_QUOTE 0xDE -#define VK_xDF 0xDF +#define VK_xDF 0xDF*/ WCHAR CharFromVK(WORD *VKey, UINT ShiftFlags); // I4582 WORD USVKToScanCodeToLayoutVK(WORD VKey); // I3762 diff --git a/windows/src/engine/mcompile/mc_syskbd.h b/windows/src/engine/mcompile/mc_syskbd.h index 11ad043933..fbbdf8d611 100644 --- a/windows/src/engine/mcompile/mc_syskbd.h +++ b/windows/src/engine/mcompile/mc_syskbd.h @@ -1,8 +1,10 @@ +#include "../../../../common/include/km_vkey.h" + #ifndef _SYSKBD_H #define _SYSKBD_H -#define VK_COLON 0xBA +/*#define VK_COLON 0xBA #define VK_EQUAL 0xBB #define VK_COMMA 0xBC #define VK_HYPHEN 0xBD @@ -13,7 +15,7 @@ #define VK_BKSLASH 0xDC #define VK_RBRKT 0xDD #define VK_QUOTE 0xDE -#define VK_xDF 0xDF +#define VK_xDF 0xDF*/ BOOL LoadNewLibrary(PWSTR filename); WCHAR CharFromVK(WORD VKey, UINT ShiftFlags, WCHAR *PDeadKey); From 97a2e81197f98bdac8f0dbb7397559084216c640 Mon Sep 17 00:00:00 2001 From: Sabine Date: Fri, 7 Aug 2026 16:24:28 +0200 Subject: [PATCH 113/140] chore(common): use km_vkey.h on mac --- mac/Keyman4Mac/Keyman4Mac/WindowsVKCodes.h | 4 ++-- .../KeymanEngine4Mac/CoreWrapper/CoreHelper.m | 3 ++- mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMEngine.m | 3 ++- .../KeymanEngine4Mac/KME/OnScreenKeyboard/OSKView.m | 3 ++- mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/WindowsVKCodes.h | 4 ++-- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/mac/Keyman4Mac/Keyman4Mac/WindowsVKCodes.h b/mac/Keyman4Mac/Keyman4Mac/WindowsVKCodes.h index b7a15b2ca9..df127aec78 100644 --- a/mac/Keyman4Mac/Keyman4Mac/WindowsVKCodes.h +++ b/mac/Keyman4Mac/Keyman4Mac/WindowsVKCodes.h @@ -6,7 +6,7 @@ // Copyright (c) 2017 SIL International. All rights reserved. // -#define VK_BACKSPACE 0x08 +/*#define VK_BACKSPACE 0x08 #define VK_TAB 0x09 #define VK_ENTER 0x0D #define VK_SPACE 0x20 @@ -76,4 +76,4 @@ #define VK_LEFT_BRACKET 0xDB #define VK_BACKSLASH 0xDC #define VK_RIGHT_BRACKET 0xDD -#define VK_QUOTE 0xDE +#define VK_QUOTE 0xDE*/ diff --git a/mac/KeymanEngine4Mac/KeymanEngine4Mac/CoreWrapper/CoreHelper.m b/mac/KeymanEngine4Mac/KeymanEngine4Mac/CoreWrapper/CoreHelper.m index c7858e7b0d..98e16f0593 100644 --- a/mac/KeymanEngine4Mac/KeymanEngine4Mac/CoreWrapper/CoreHelper.m +++ b/mac/KeymanEngine4Mac/KeymanEngine4Mac/CoreWrapper/CoreHelper.m @@ -18,7 +18,8 @@ #import "CoreAction.h" #import "keyman_core_api.h" #import "MacVKCodes.h" -#import "WindowsVKCodes.h" +#import "km_vkey.h" +//#import "WindowsVKCodes.h" #import "KMELogs.h" const int VIRTUAL_KEY_ARRAY_SIZE = 0x80; diff --git a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMEngine.m b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMEngine.m index fd59c48b62..6641f71425 100644 --- a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMEngine.m +++ b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMEngine.m @@ -8,7 +8,8 @@ #import "KMEngine.h" #import "KMBinaryFileFormat.h" -#import "WindowsVKCodes.h" +//#import "WindowsVKCodes.h" +#import "km_vkey.h" #import "MacVKCodes.h" #import "CoreWrapper.h" #import "KMELogs.h" diff --git a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/OnScreenKeyboard/OSKView.m b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/OnScreenKeyboard/OSKView.m index e576d000b8..03f3f66d1c 100644 --- a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/OnScreenKeyboard/OSKView.m +++ b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/OnScreenKeyboard/OSKView.m @@ -10,7 +10,8 @@ #import "OSKKey.h" #import "KeyView.h" #import "MacVKCodes.h" -#import "WindowsVKCodes.h" +//#import "WindowsVKCodes.h" +#import "km_vkey.h" #import "NKey.h" #import "CoreHelper.h" diff --git a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/WindowsVKCodes.h b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/WindowsVKCodes.h index 2fc5ba7f79..9650ff93d6 100644 --- a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/WindowsVKCodes.h +++ b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/WindowsVKCodes.h @@ -6,7 +6,7 @@ // Copyright (c) 2017 SIL International. All rights reserved. // -#ifndef WindowsVKCodes_h +/*#ifndef WindowsVKCodes_h #define WindowsVKCodes_h #define VK_BACKSPACE 0x08 @@ -83,4 +83,4 @@ #define VK_OEM_102 0xE2 -#endif /* WindowsVKCodes_h */ +#endif *//* WindowsVKCodes_h */ From cbe5d40d62a3290b38ebf9507619a72f8d957fd5 Mon Sep 17 00:00:00 2001 From: Sabine Date: Fri, 7 Aug 2026 16:29:03 +0200 Subject: [PATCH 114/140] chore(common): undo use km_vkey.h on mac --- mac/Keyman4Mac/Keyman4Mac/WindowsVKCodes.h | 4 ++-- .../KeymanEngine4Mac/CoreWrapper/CoreHelper.m | 3 +-- mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMEngine.m | 3 +-- .../KeymanEngine4Mac/KME/OnScreenKeyboard/OSKView.m | 3 +-- mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/WindowsVKCodes.h | 4 ++-- 5 files changed, 7 insertions(+), 10 deletions(-) diff --git a/mac/Keyman4Mac/Keyman4Mac/WindowsVKCodes.h b/mac/Keyman4Mac/Keyman4Mac/WindowsVKCodes.h index df127aec78..b7a15b2ca9 100644 --- a/mac/Keyman4Mac/Keyman4Mac/WindowsVKCodes.h +++ b/mac/Keyman4Mac/Keyman4Mac/WindowsVKCodes.h @@ -6,7 +6,7 @@ // Copyright (c) 2017 SIL International. All rights reserved. // -/*#define VK_BACKSPACE 0x08 +#define VK_BACKSPACE 0x08 #define VK_TAB 0x09 #define VK_ENTER 0x0D #define VK_SPACE 0x20 @@ -76,4 +76,4 @@ #define VK_LEFT_BRACKET 0xDB #define VK_BACKSLASH 0xDC #define VK_RIGHT_BRACKET 0xDD -#define VK_QUOTE 0xDE*/ +#define VK_QUOTE 0xDE diff --git a/mac/KeymanEngine4Mac/KeymanEngine4Mac/CoreWrapper/CoreHelper.m b/mac/KeymanEngine4Mac/KeymanEngine4Mac/CoreWrapper/CoreHelper.m index 98e16f0593..c7858e7b0d 100644 --- a/mac/KeymanEngine4Mac/KeymanEngine4Mac/CoreWrapper/CoreHelper.m +++ b/mac/KeymanEngine4Mac/KeymanEngine4Mac/CoreWrapper/CoreHelper.m @@ -18,8 +18,7 @@ #import "CoreAction.h" #import "keyman_core_api.h" #import "MacVKCodes.h" -#import "km_vkey.h" -//#import "WindowsVKCodes.h" +#import "WindowsVKCodes.h" #import "KMELogs.h" const int VIRTUAL_KEY_ARRAY_SIZE = 0x80; diff --git a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMEngine.m b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMEngine.m index 6641f71425..fd59c48b62 100644 --- a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMEngine.m +++ b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMEngine.m @@ -8,8 +8,7 @@ #import "KMEngine.h" #import "KMBinaryFileFormat.h" -//#import "WindowsVKCodes.h" -#import "km_vkey.h" +#import "WindowsVKCodes.h" #import "MacVKCodes.h" #import "CoreWrapper.h" #import "KMELogs.h" diff --git a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/OnScreenKeyboard/OSKView.m b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/OnScreenKeyboard/OSKView.m index 03f3f66d1c..e576d000b8 100644 --- a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/OnScreenKeyboard/OSKView.m +++ b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/OnScreenKeyboard/OSKView.m @@ -10,8 +10,7 @@ #import "OSKKey.h" #import "KeyView.h" #import "MacVKCodes.h" -//#import "WindowsVKCodes.h" -#import "km_vkey.h" +#import "WindowsVKCodes.h" #import "NKey.h" #import "CoreHelper.h" diff --git a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/WindowsVKCodes.h b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/WindowsVKCodes.h index 9650ff93d6..2fc5ba7f79 100644 --- a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/WindowsVKCodes.h +++ b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/WindowsVKCodes.h @@ -6,7 +6,7 @@ // Copyright (c) 2017 SIL International. All rights reserved. // -/*#ifndef WindowsVKCodes_h +#ifndef WindowsVKCodes_h #define WindowsVKCodes_h #define VK_BACKSPACE 0x08 @@ -83,4 +83,4 @@ #define VK_OEM_102 0xE2 -#endif *//* WindowsVKCodes_h */ +#endif /* WindowsVKCodes_h */ From 58915062f80051987f2800498a3b3f7acfc115ad Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Mon, 10 Aug 2026 14:15:36 +0200 Subject: [PATCH 115/140] docs(web): add language identifier to the code blocks of event docs Build-bot: skip Test-bot: skip --- web/docs/engine/reference/events/keyman/beforekeyboardchange.md | 2 +- web/docs/engine/reference/events/keyman/controlblurred.md | 2 +- web/docs/engine/reference/events/keyman/controlfocused.md | 2 +- web/docs/engine/reference/events/keyman/keyboardchange.md | 2 +- web/docs/engine/reference/events/keyman/keyboardloaded.md | 2 +- web/docs/engine/reference/events/keyman/keyboardregistered.md | 2 +- web/docs/engine/reference/events/keyman/loaduserinterface.md | 2 +- web/docs/engine/reference/events/keyman/unloaduserinterface.md | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/web/docs/engine/reference/events/keyman/beforekeyboardchange.md b/web/docs/engine/reference/events/keyman/beforekeyboardchange.md index 6fad014400..25a9133eb5 100644 --- a/web/docs/engine/reference/events/keyman/beforekeyboardchange.md +++ b/web/docs/engine/reference/events/keyman/beforekeyboardchange.md @@ -8,7 +8,7 @@ Called when keyboard input language is about to change. ## Syntax -``` +```javascript keyman.addEventListener('beforekeyboardchange', function(keyboardProperties) { ... }); diff --git a/web/docs/engine/reference/events/keyman/controlblurred.md b/web/docs/engine/reference/events/keyman/controlblurred.md index bb42a2b548..a31a814baa 100644 --- a/web/docs/engine/reference/events/keyman/controlblurred.md +++ b/web/docs/engine/reference/events/keyman/controlblurred.md @@ -8,7 +8,7 @@ Called when an input element loses focus. ## Syntax -``` +```javascript keyman.addEventListener('controlblurred', function(eventProperties) { ... }); diff --git a/web/docs/engine/reference/events/keyman/controlfocused.md b/web/docs/engine/reference/events/keyman/controlfocused.md index 34525f0fed..6135fcbc3c 100644 --- a/web/docs/engine/reference/events/keyman/controlfocused.md +++ b/web/docs/engine/reference/events/keyman/controlfocused.md @@ -8,7 +8,7 @@ Called when an input element receives focus. ## Syntax -``` +```javascript keyman.addEventListener('controlfocused', function(eventProperties) { ... }); diff --git a/web/docs/engine/reference/events/keyman/keyboardchange.md b/web/docs/engine/reference/events/keyman/keyboardchange.md index 0d4570257f..1075b2e4d6 100644 --- a/web/docs/engine/reference/events/keyman/keyboardchange.md +++ b/web/docs/engine/reference/events/keyman/keyboardchange.md @@ -8,7 +8,7 @@ Called when keyboard input language changed. ## Syntax -``` +```javascript keyman.addEventListener('keyboardchange', function(keyboardProperties) { ... }); diff --git a/web/docs/engine/reference/events/keyman/keyboardloaded.md b/web/docs/engine/reference/events/keyman/keyboardloaded.md index 7b0a65b882..a605f4d783 100644 --- a/web/docs/engine/reference/events/keyman/keyboardloaded.md +++ b/web/docs/engine/reference/events/keyman/keyboardloaded.md @@ -8,7 +8,7 @@ Called when keyboard code loaded. ## Syntax -``` +```javascript keyman.addEventListener('keyboardloaded', function(keyboardProperties) { ... }); diff --git a/web/docs/engine/reference/events/keyman/keyboardregistered.md b/web/docs/engine/reference/events/keyman/keyboardregistered.md index b92ca767d9..cf4d546063 100644 --- a/web/docs/engine/reference/events/keyman/keyboardregistered.md +++ b/web/docs/engine/reference/events/keyman/keyboardregistered.md @@ -9,7 +9,7 @@ keyboard). ## Syntax -``` +```javascript keyman.addEventListener('keyboardregistered', function(keyboardProperties) { ... }); diff --git a/web/docs/engine/reference/events/keyman/loaduserinterface.md b/web/docs/engine/reference/events/keyman/loaduserinterface.md index 094df102e5..8893b45fd1 100644 --- a/web/docs/engine/reference/events/keyman/loaduserinterface.md +++ b/web/docs/engine/reference/events/keyman/loaduserinterface.md @@ -8,7 +8,7 @@ Called to initiate the ui initialization process. ## Syntax -``` +```javascript keyman.addEventListener('loaduserinterface', function() { ... }); diff --git a/web/docs/engine/reference/events/keyman/unloaduserinterface.md b/web/docs/engine/reference/events/keyman/unloaduserinterface.md index db44244b5b..88a9ca9064 100644 --- a/web/docs/engine/reference/events/keyman/unloaduserinterface.md +++ b/web/docs/engine/reference/events/keyman/unloaduserinterface.md @@ -8,7 +8,7 @@ Called when allow ui clean-up. ## Syntax -``` +```javascript keyman.addEventListener('unloaduserinterface', function() { ... }); From bbf0eaefef84b6c236053b4de04d530ebcbd7d4a Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Mon, 10 Aug 2026 13:01:55 -0500 Subject: [PATCH 116/140] auto: increment master version to 19.0.264 Test-bot: skip Build-bot: skip --- HISTORY.md | 7 +++++++ VERSION.md | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index e348de89f3..4fc8d34321 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,12 @@ # Keyman Version History +## 19.0.263 alpha 2026-08-10 + +* chore(deps): bump tar from 7.5.11 to 7.5.22 in /developer/src/server/src/win32/trayicon/addon-src (#16282) +* chore(deps): bump brace-expansion from 5.0.6 to 5.0.9 in /developer/src/server/src/win32/trayicon/addon-src (#16295) +* refactor(web): improve naming of `_SetTargetDir` function (#16311) +* docs(web): add language identifier to the code blocks of event docs (#16333) + ## 19.0.262 alpha 2026-08-04 * chore(web): fire `keyboardloaded` event only once per keyboard (#16297) diff --git a/VERSION.md b/VERSION.md index f00ee4d41a..72e1993803 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -19.0.263 \ No newline at end of file +19.0.264 \ No newline at end of file From 5be5eb4689aff1a9a723c2be967dad8a18b6ac15 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Tue, 11 Aug 2026 16:06:28 +1000 Subject: [PATCH 117/140] fix(core): unit test action_que insert terminated Adds the KM_CORE_IT_END terminator to action_clone test before calling km_core_state_queue_action_items Fixes:#16341 --- core/tests/unit/kmnkbd/state_api.tests.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/core/tests/unit/kmnkbd/state_api.tests.cpp b/core/tests/unit/kmnkbd/state_api.tests.cpp index d500a1bd06..9bef122534 100644 --- a/core/tests/unit/kmnkbd/state_api.tests.cpp +++ b/core/tests/unit/kmnkbd/state_api.tests.cpp @@ -423,7 +423,13 @@ action_clone.option = &clone_persist_opt; if (test_clone_2->actions().back().type == KM_CORE_IT_END) { test_clone_2->actions().pop_back(); } -km_core_state_queue_action_items(test_clone_2, &action_clone); + +km_core_action_item action_clone_queue[] = { + action_clone, + {KM_CORE_IT_END} +}; + +km_core_state_queue_action_items(test_clone_2, action_clone_queue); test_clone_2->actions().commit(); // Test debug dump From 16e6a5634c9b17e570a448e491a8f6333ad61cd4 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Tue, 11 Aug 2026 16:23:56 +1000 Subject: [PATCH 118/140] fix(core): make coherence check a debug assert This also adds a null pointer check to KM_CORE_IT_PERSIST_OPT case in km::core::action_item_list_to_actions_object Fixes: #16340 --- core/src/action.cpp | 4 ++++ core/src/state.cpp | 9 +++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/core/src/action.cpp b/core/src/action.cpp index ca6b0b30d8..eb2c4cc66c 100644 --- a/core/src/action.cpp +++ b/core/src/action.cpp @@ -103,6 +103,10 @@ bool km::core::action_item_list_to_actions_object( break; case KM_CORE_IT_PERSIST_OPT: { + assert(action_items->option != nullptr); + if(action_items->option != nullptr) { + return false; + } // TODO: lowpri: replace existing item if already present in options vector? km::core::option opt(static_cast(action_items->option->scope), action_items->option->key, diff --git a/core/src/state.cpp b/core/src/state.cpp index 3bf9efb358..d06f0f6e60 100644 --- a/core/src/state.cpp +++ b/core/src/state.cpp @@ -44,15 +44,12 @@ actions::actions(actions const &other) size_t opt_index = 0; for (auto &item : *this) { if (item.type == KM_CORE_IT_PERSIST_OPT) { - if (opt_index < _option_items_stack.size()) { - item.option = &_option_items_stack[opt_index++]; - } else { - // no matching item in the stack; clear pointer. - item.option = nullptr; - } + assert(opt_index < _option_items_stack.size()); + item.option = &_option_items_stack[opt_index++]; } } } +} state::state(km::core::abstract_processor & ap, km_core_option_item const *env) : _processor(ap) From f8be03cf8bdd7cb18ac333b618d3a5fbb4825238 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Tue, 11 Aug 2026 16:59:10 +1000 Subject: [PATCH 119/140] fix(core): fix typo plus temp remove null check --- core/src/action.cpp | 8 ++++---- core/src/state.cpp | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/core/src/action.cpp b/core/src/action.cpp index eb2c4cc66c..2fe7694ccc 100644 --- a/core/src/action.cpp +++ b/core/src/action.cpp @@ -103,10 +103,10 @@ bool km::core::action_item_list_to_actions_object( break; case KM_CORE_IT_PERSIST_OPT: { - assert(action_items->option != nullptr); - if(action_items->option != nullptr) { - return false; - } + // assert(action_items->option != nullptr); + // if(action_items->option != nullptr) { + // return false; + // } // TODO: lowpri: replace existing item if already present in options vector? km::core::option opt(static_cast(action_items->option->scope), action_items->option->key, diff --git a/core/src/state.cpp b/core/src/state.cpp index d06f0f6e60..fcc6578914 100644 --- a/core/src/state.cpp +++ b/core/src/state.cpp @@ -49,7 +49,6 @@ actions::actions(actions const &other) } } } -} state::state(km::core::abstract_processor & ap, km_core_option_item const *env) : _processor(ap) From 8b0f6d3297fafd4269bd60eba4e89b34ab1b7f12 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Tue, 11 Aug 2026 17:29:18 +1000 Subject: [PATCH 120/140] fix(core): add null check --- core/src/action.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/action.cpp b/core/src/action.cpp index 2fe7694ccc..f76634ea0c 100644 --- a/core/src/action.cpp +++ b/core/src/action.cpp @@ -103,10 +103,10 @@ bool km::core::action_item_list_to_actions_object( break; case KM_CORE_IT_PERSIST_OPT: { - // assert(action_items->option != nullptr); - // if(action_items->option != nullptr) { - // return false; - // } + assert(action_items->option != nullptr); + if(action_items->option == nullptr) { + return false; + } // TODO: lowpri: replace existing item if already present in options vector? km::core::option opt(static_cast(action_items->option->scope), action_items->option->key, From a2bf976e5e1142a6d0c6a376fe49151fb6ff4777 Mon Sep 17 00:00:00 2001 From: Sabine Date: Tue, 11 Aug 2026 09:46:49 +0200 Subject: [PATCH 121/140] chore(common): remove commented old #defines; in some files remove paths for #include km_vkey --- common/include/km_vkey.h | 5 +-- core/src/kmx/kmx_base.h | 2 +- core/src/kmx/kmx_conversion.h | 14 -------- core/src/kmx/kmx_processor.cpp | 1 - .../src/common/web/utils/src/xml-utils.ts | 2 +- developer/src/kmanalyze/kmanalyze.cpp | 15 -------- .../src/kmcmplib/src/CharToKeyConversion.cpp | 22 +----------- developer/src/kmcmplib/src/Compiler.cpp | 6 ++-- developer/src/kmdecomp/savekeyboard.cpp | 1 + developer/src/samples/imsample/imsample.cpp | 6 ---- linux/mcompile/keymap/keymap.h | 35 ------------------- mac/mcompile/keymap.h | 35 ------------------- windows/src/engine/keyman32/syskbd.h | 16 +-------- windows/src/engine/mcompile/mc_syskbd.h | 13 ------- 14 files changed, 9 insertions(+), 164 deletions(-) diff --git a/common/include/km_vkey.h b/common/include/km_vkey.h index 8830d22213..8f53774f90 100644 --- a/common/include/km_vkey.h +++ b/common/include/km_vkey.h @@ -56,9 +56,6 @@ namespace vkey { #define VK_EXECUTE 0x2B #define VK_SNAPSHOT 0x2C #define VK_INSERT 0x2D -// #define VK_DECIMAL 0x2E // _S2 if I use it here kmdecomp complains because of duplicate macro definition here and in WinUser.h -// #define VK_DECIMAL 0x2E //_S2 probably wrong 0x2E (which is actually the Windows VK_DELETE value, not the standard VK_DECIMAL -// which is 0x6E for the numpad decimal key) #define VK_DECIMAL 0x6E #define VK_KEY_0 0x30 @@ -160,4 +157,4 @@ namespace vkey { #define VK_OEM_CLEAR 0xFE } // namespace vkey -} // namespace km \ No newline at end of file +} // namespace km diff --git a/core/src/kmx/kmx_base.h b/core/src/kmx/kmx_base.h index c2c6fb5722..30b9ab5c75 100644 --- a/core/src/kmx/kmx_base.h +++ b/core/src/kmx/kmx_base.h @@ -1,7 +1,7 @@ #pragma once #include "keyman_core.h" -#include "../../../common/include/km_vkey.h" +#include "km_vkey.h" #if defined(_WIN32) || defined(_WIN64) #define snprintf _snprintf diff --git a/core/src/kmx/kmx_conversion.h b/core/src/kmx/kmx_conversion.h index 66f8ff31d6..3ef4d0e413 100644 --- a/core/src/kmx/kmx_conversion.h +++ b/core/src/kmx/kmx_conversion.h @@ -6,20 +6,6 @@ namespace km { namespace core { namespace kmx { -/*#define VK_SPACE 0x20 -#define VK_COLON 0xBA -#define VK_EQUAL 0xBB -#define VK_COMMA 0xBC -#define VK_HYPHEN 0xBD -#define VK_PERIOD 0xBE -#define VK_SLASH 0xBF -#define VK_ACCENT 0xC0 -#define VK_LBRKT 0xDB -#define VK_BKSLASH 0xDC -#define VK_RBRKT 0xDD -#define VK_QUOTE 0xDE -#define VK_xDF 0xDF*/ - /** * This maps an ASCII character corresponding to a US EN key cap to a * Virtual Key and correctly sets the Shift BIT in the ShiftFlag DWORD. diff --git a/core/src/kmx/kmx_processor.cpp b/core/src/kmx/kmx_processor.cpp index 293158a090..42d2d8dd4e 100644 --- a/core/src/kmx/kmx_processor.cpp +++ b/core/src/kmx/kmx_processor.cpp @@ -7,7 +7,6 @@ using namespace km::core; using namespace kmx; using namespace km::vkey; - static KMX_BOOL ContextItemsFromAppContext(KMX_WCHAR *buf, km_core_context_item** outPtr) { assert(buf); diff --git a/developer/src/common/web/utils/src/xml-utils.ts b/developer/src/common/web/utils/src/xml-utils.ts index 8f1fa6a88c..2baeb7c6af 100644 --- a/developer/src/common/web/utils/src/xml-utils.ts +++ b/developer/src/common/web/utils/src/xml-utils.ts @@ -396,4 +396,4 @@ export function withOffset(c: number, compileContext?: any) : KeymanXMLMetadata startIndex: c }); return o; -} \ No newline at end of file +} diff --git a/developer/src/kmanalyze/kmanalyze.cpp b/developer/src/kmanalyze/kmanalyze.cpp index ed8e7c7d48..ef3009a348 100644 --- a/developer/src/kmanalyze/kmanalyze.cpp +++ b/developer/src/kmanalyze/kmanalyze.cpp @@ -603,21 +603,6 @@ void PrintTree(LPKEYBOARD kbd, GROUPTREE *t, int depth) { } } - -// MapVirtualKeys is copied from syskbd.cpp (keyman32) -/*#define VK_COLON 0xBA -#define VK_EQUAL 0xBB -#define VK_COMMA 0xBC -#define VK_HYPHEN 0xBD -#define VK_PERIOD 0xBE -#define VK_SLASH 0xBF -#define VK_ACCENT 0xC0 -#define VK_LBRKT 0xDB -#define VK_BKSLASH 0xDC -#define VK_RBRKT 0xDD -#define VK_QUOTE 0xDE -#define VK_xDF 0xDF*/ - WCHAR MapVirtualKeys(WORD keyCode, UINT shiftFlags) { char shiftedDigit[] = ")!@#$%^&*("; diff --git a/developer/src/kmcmplib/src/CharToKeyConversion.cpp b/developer/src/kmcmplib/src/CharToKeyConversion.cpp index 5aaed7d409..fe87d146e3 100644 --- a/developer/src/kmcmplib/src/CharToKeyConversion.cpp +++ b/developer/src/kmcmplib/src/CharToKeyConversion.cpp @@ -1,27 +1,7 @@ #include "pch.h" #include "CharToKeyConversion.h" -#include "../../../../common/include/km_vkey.h" - -/* Following VKeys come from Windows */ - -/*#define VK_COLON 0xBA -#define VK_EQUAL 0xBB -#define VK_COMMA 0xBC -#define VK_HYPHEN 0xBD -#define VK_PERIOD 0xBE -#define VK_SLASH 0xBF -#define VK_ACCENT 0xC0 -#define VK_LBRKT 0xDB -#define VK_BKSLASH 0xDC -#define VK_RBRKT 0xDD -#define VK_QUOTE 0xDE -#define VK_xDF 0xDF - -#define VK_NUMPAD0 0x60 -#define VK_NUMPAD9 0x69 -#define VK_SPACE 0x20*/ - +#include "km_vkey.h" /* Following code lifted from syskbd.cpp and tweaked for compiler use. Todo: consolidate */ diff --git a/developer/src/kmcmplib/src/Compiler.cpp b/developer/src/kmcmplib/src/Compiler.cpp index 2d3cf2b179..d1e0d766ba 100644 --- a/developer/src/kmcmplib/src/Compiler.cpp +++ b/developer/src/kmcmplib/src/Compiler.cpp @@ -77,9 +77,9 @@ #include "cp1252.h" #include "virtualcharkeys.h" -#include "../../../../common/include/km_vkey.h" -#include "../../../../common/include/vkeys.h" -#include "../../../../common/include/keymanversion.h" +#include "km_vkey.h" +#include "vkeys.h" +#include "keymanversion.h" #include "crc32.h" #include "debugstore.h" diff --git a/developer/src/kmdecomp/savekeyboard.cpp b/developer/src/kmdecomp/savekeyboard.cpp index 2b70b7ffbb..1160ef0c1b 100644 --- a/developer/src/kmdecomp/savekeyboard.cpp +++ b/developer/src/kmdecomp/savekeyboard.cpp @@ -34,6 +34,7 @@ #include "../../../common/windows/cpp/include/legacy_kmx_memory.h" #include "../../../common/include/vkeys.h" #include "../../../common/include/km_vkey.h" + using namespace km::vkey; #define SSN__PREFIX L"&" diff --git a/developer/src/samples/imsample/imsample.cpp b/developer/src/samples/imsample/imsample.cpp index 61e9489df3..f1993c53df 100644 --- a/developer/src/samples/imsample/imsample.cpp +++ b/developer/src/samples/imsample/imsample.cpp @@ -22,12 +22,6 @@ #include #include "imlib.h" -// km_vkey.h is not used here because duplicate definitions of VK codes in km_vkey.h and might -// produce macro redefinition warnings. (in case we ever need VK_ESCAPE or VK_BACK on mac or linux the -// corresponding values are: VK_ESCAPE =0x1B; VK_BACK=0x08) -// #include "../../../../common/include/km_vkey.h" -// using namespace km::vkey; - HWND hwnd; HWND hwndChild; HINSTANCE hinst; diff --git a/linux/mcompile/keymap/keymap.h b/linux/mcompile/keymap/keymap.h index bd3e37aef8..f39d501d70 100644 --- a/linux/mcompile/keymap/keymap.h +++ b/linux/mcompile/keymap/keymap.h @@ -17,7 +17,6 @@ #include #include "km_vkey.h" - enum ShiftState { Base = 0, // 0 Shft = 1, // 1 @@ -31,40 +30,6 @@ enum ShiftState { ShftXxxx = Shft | Xxxx, // 9 }; -/*#define VK_SPACE 0x20 -#define VK_COLON 0xBA -#define VK_EQUAL 0xBB -#define VK_COMMA 0xBC -#define VK_HYPHEN 0xBD -#define VK_PERIOD 0xBE -#define VK_SLASH 0xBF -#define VK_ACCENT 0xC0 -#define VK_LBRKT 0xDB -#define VK_BKSLASH 0xDC -#define VK_RBRKT 0xDD -#define VK_QUOTE 0xDE -#define VK_xDF 0xDF -#define VK_OEM_102 0xE2 // "<>" or "\|" on RT 102-key kbd. - -#define VK_DIVIDE 0x6F -#define VK_CANCEL 0x03 -//#define VK_DECIMAL 0x2E //_S2 probably wrong 0x2E (which is actually the Windows VK_DELETE value, not the standard VK_DECIMAL which is 0x6E for the numpad decimal key) -#define VK_DECIMAL 0x6E - -#define VK_OEM_CLEAR 0xFE -#define VK_LSHIFT 0xA0 -#define VK_RSHIFT 0xA1 -#define VK_LCONTROL 0xA2 -#define VK_RCONTROL 0xA3 -#define VK_LMENU 0xA4 -#define VK_RMENU 0xA5 - -#define VK_SHIFT 0x10 -#define VK_CONTROL 0x11 -#define VK_MENU 0x12 -#define VK_PAUSE 0x13 -#define VK_CAPITAL 0x14*/ - typedef std::vector vec_string_1D; typedef std::vector vec_dword_1D; typedef std::vector > vec_dword_2D; diff --git a/mac/mcompile/keymap.h b/mac/mcompile/keymap.h index d3e9595b19..d09645f747 100644 --- a/mac/mcompile/keymap.h +++ b/mac/mcompile/keymap.h @@ -6,7 +6,6 @@ #include #include #include - #include "../../common/include/km_u16.h" #include "km_vkey.h" @@ -23,40 +22,6 @@ enum ShiftState { ShftXxxx = Shft | Xxxx, // 9 }; -/*#define VK_SPACE 0x20 -#define VK_COLON 0xBA -#define VK_EQUAL 0xBB -#define VK_COMMA 0xBC -#define VK_HYPHEN 0xBD -#define VK_PERIOD 0xBE -#define VK_SLASH 0xBF -#define VK_ACCENT 0xC0 -#define VK_LBRKT 0xDB -#define VK_BKSLASH 0xDC -#define VK_RBRKT 0xDD -#define VK_QUOTE 0xDE -#define VK_xDF 0xDF -#define VK_OEM_102 0xE2 // "<>" or "\|" on RT 102-key kbd. - -#define VK_DIVIDE 0x6F -#define VK_CANCEL 0x03 -//#define VK_DECIMAL 0x2E //_S2 probably wrong 0x2E (which is actually the Windows VK_DELETE value, not the standard VK_DECIMAL which is 0x6E for the numpad decimal key) -#define VK_DECIMAL 0x6E - -#define VK_OEM_CLEAR 0xFE -#define VK_LSHIFT 0xA0 -#define VK_RSHIFT 0xA1 -#define VK_LCONTROL 0xA2 -#define VK_RCONTROL 0xA3 -#define VK_LMENU 0xA4 -#define VK_RMENU 0xA5 - -#define VK_SHIFT 0x10 -#define VK_CONTROL 0x11 -#define VK_MENU 0x12 -#define VK_PAUSE 0x13 -#define VK_CAPITAL 0x14*/ - typedef std::vector vec_string_1D; typedef std::vector vec_dword_1D; typedef std::vector > vec_dword_2D; diff --git a/windows/src/engine/keyman32/syskbd.h b/windows/src/engine/keyman32/syskbd.h index 18d83fc2ae..35c7b72a16 100644 --- a/windows/src/engine/keyman32/syskbd.h +++ b/windows/src/engine/keyman32/syskbd.h @@ -22,21 +22,7 @@ #ifndef __SYSKBD_H #define __SYSKBD_H -#include "../../../../common/include/km_vkey.h" - - -/*#define VK_COLON 0xBA -#define VK_EQUAL 0xBB -#define VK_COMMA 0xBC -#define VK_HYPHEN 0xBD -#define VK_PERIOD 0xBE -#define VK_SLASH 0xBF -#define VK_ACCENT 0xC0 -#define VK_LBRKT 0xDB -#define VK_BKSLASH 0xDC -#define VK_RBRKT 0xDD -#define VK_QUOTE 0xDE -#define VK_xDF 0xDF*/ +#include "km_vkey.h" WCHAR CharFromVK(WORD *VKey, UINT ShiftFlags); // I4582 WORD USVKToScanCodeToLayoutVK(WORD VKey); // I3762 diff --git a/windows/src/engine/mcompile/mc_syskbd.h b/windows/src/engine/mcompile/mc_syskbd.h index fbbdf8d611..0ed8882a6f 100644 --- a/windows/src/engine/mcompile/mc_syskbd.h +++ b/windows/src/engine/mcompile/mc_syskbd.h @@ -4,19 +4,6 @@ #ifndef _SYSKBD_H #define _SYSKBD_H -/*#define VK_COLON 0xBA -#define VK_EQUAL 0xBB -#define VK_COMMA 0xBC -#define VK_HYPHEN 0xBD -#define VK_PERIOD 0xBE -#define VK_SLASH 0xBF -#define VK_ACCENT 0xC0 -#define VK_LBRKT 0xDB -#define VK_BKSLASH 0xDC -#define VK_RBRKT 0xDD -#define VK_QUOTE 0xDE -#define VK_xDF 0xDF*/ - BOOL LoadNewLibrary(PWSTR filename); WCHAR CharFromVK(WORD VKey, UINT ShiftFlags, WCHAR *PDeadKey); WORD VKUSToVKUnderlyingLayout(WORD VKey); From 4e4e37ec26401f497bb3d92656c81d2076d4a541 Mon Sep 17 00:00:00 2001 From: Sabine Date: Tue, 11 Aug 2026 11:13:51 +0200 Subject: [PATCH 122/140] chore(common): add VK_DELETE to km_vkey.h --- common/include/km_vkey.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/include/km_vkey.h b/common/include/km_vkey.h index 8f53774f90..438a283b51 100644 --- a/common/include/km_vkey.h +++ b/common/include/km_vkey.h @@ -56,7 +56,7 @@ namespace vkey { #define VK_EXECUTE 0x2B #define VK_SNAPSHOT 0x2C #define VK_INSERT 0x2D -#define VK_DECIMAL 0x6E +#define VK_DELETE 0x2E #define VK_KEY_0 0x30 #define VK_KEY_1 0x31 @@ -111,6 +111,7 @@ namespace vkey { #define VK_NUMPAD_SUBTRACT 0x6D #define VK_NUMPAD_DECIMAL 0x6E +#define VK_DECIMAL 0x6E #define VK_NUMPAD_DIVIDE 0x6F #define VK_DIVIDE 0x6F From 4522e087a3f912b23d62bee192585e7a676757dc Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Tue, 11 Aug 2026 20:41:15 +1000 Subject: [PATCH 123/140] fix(core): fix indenting in state_api_tests --- core/tests/unit/kmnkbd/state_api.tests.cpp | 58 +++++++++++----------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/core/tests/unit/kmnkbd/state_api.tests.cpp b/core/tests/unit/kmnkbd/state_api.tests.cpp index 9bef122534..0ecc1787cf 100644 --- a/core/tests/unit/kmnkbd/state_api.tests.cpp +++ b/core/tests/unit/kmnkbd/state_api.tests.cpp @@ -413,42 +413,42 @@ int main(int argc, char * argv[]) clone_state_deleted_text )); -// Add two actions before cloning the state again -try_status(km_core_process_event(test_state, KM_CORE_VKEY_F3, 0, 1, KM_CORE_EVENT_FLAG_DEFAULT)); -try_status(km_core_state_clone(test_state, &test_clone_2)); + // Add two actions before cloning the state again + try_status(km_core_process_event(test_state, KM_CORE_VKEY_F3, 0, 1, KM_CORE_EVENT_FLAG_DEFAULT)); + try_status(km_core_state_clone(test_state, &test_clone_2)); -// Now put an option in the test_clone_2 state only -km_core_action_item action_clone = {KM_CORE_IT_PERSIST_OPT, {0,}, }; -action_clone.option = &clone_persist_opt; -if (test_clone_2->actions().back().type == KM_CORE_IT_END) { - test_clone_2->actions().pop_back(); -} + // Now put an option in the test_clone_2 state only + km_core_action_item action_clone = {KM_CORE_IT_PERSIST_OPT, {0,}, }; + action_clone.option = &clone_persist_opt; + if (test_clone_2->actions().back().type == KM_CORE_IT_END) { + test_clone_2->actions().pop_back(); + } -km_core_action_item action_clone_queue[] = { - action_clone, - {KM_CORE_IT_END} -}; + km_core_action_item action_clone_queue[] = { + action_clone, + {KM_CORE_IT_END} + }; -km_core_state_queue_action_items(test_clone_2, action_clone_queue); -test_clone_2->actions().commit(); + km_core_state_queue_action_items(test_clone_2, action_clone_queue); + test_clone_2->actions().commit(); - // Test debug dump -auto doc3 = get_json_doc(*test_state), doc4 = get_json_doc(*test_clone_2); -std::cout << "doc3:" << std::endl; -std::cout << doc3 << std::endl; -std::cout << "doc4:" << std::endl; -std::cout << doc4 << std::endl; -if (doc3 == doc4) return __LINE__; + // Test debug dump + auto doc3 = get_json_doc(*test_state), doc4 = get_json_doc(*test_clone_2); + std::cout << "doc3:" << std::endl; + std::cout << doc3 << std::endl; + std::cout << "doc4:" << std::endl; + std::cout << doc4 << std::endl; + if (doc3 == doc4) return __LINE__; -km_core_action_item action_tp3 = {KM_CORE_IT_PERSIST_OPT, {0,}, }; -action_tp3.option = &test_point_3_opt; + km_core_action_item action_tp3 = {KM_CORE_IT_PERSIST_OPT, {0,}, }; + action_tp3.option = &test_point_3_opt; -km_core_action_item action_tp4 = {KM_CORE_IT_PERSIST_OPT, {0,}, }; -action_tp4.option = &test_point_4_opt; + km_core_action_item action_tp4 = {KM_CORE_IT_PERSIST_OPT, {0,}, }; + action_tp4.option = &test_point_4_opt; -test_assert(action_items(test_state, {action_tp3, action_tp4, {KM_CORE_IT_END}})); -// Check that test_clone_2 has the same persisted options plus the extra queued option. -test_assert(action_items(test_clone_2, {action_tp3, action_tp4, action_clone, {KM_CORE_IT_END}})); + test_assert(action_items(test_state, {action_tp3, action_tp4, {KM_CORE_IT_END}})); + // Check that test_clone_2 has the same persisted options plus the extra queued option. + test_assert(action_items(test_clone_2, {action_tp3, action_tp4, action_clone, {KM_CORE_IT_END}})); // Destroy them km_core_state_dispose(test_state); From f1acc9b7f1bb8ea03224698e328b416138b540cf Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Tue, 11 Aug 2026 13:01:40 -0500 Subject: [PATCH 124/140] auto: increment master version to 19.0.265 Test-bot: skip Build-bot: skip --- HISTORY.md | 5 +++++ VERSION.md | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 4fc8d34321..262fac91c4 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,10 @@ # Keyman Version History +## 19.0.264 alpha 2026-08-11 + +* docs(android): update keyboardharness readme (#16220) +* feat(android): design improvement for settings, add dark mode and tool menu (#15827) + ## 19.0.263 alpha 2026-08-10 * chore(deps): bump tar from 7.5.11 to 7.5.22 in /developer/src/server/src/win32/trayicon/addon-src (#16282) diff --git a/VERSION.md b/VERSION.md index 72e1993803..6565e975a7 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -19.0.264 \ No newline at end of file +19.0.265 \ No newline at end of file From a621106ef30b8181346414b7483c571c087ea1ad Mon Sep 17 00:00:00 2001 From: Sabine Date: Wed, 12 Aug 2026 10:04:20 +0200 Subject: [PATCH 125/140] chore(common): remove namespace km::vkey --- common/include/km_vkey.h | 6 -- core/src/kmx/kmx_actions.cpp | 1 - core/src/kmx/kmx_capslock.cpp | 1 - core/src/kmx/kmx_context.cpp | 1 - core/src/kmx/kmx_conversion.cpp | 1 - core/src/kmx/kmx_debugger.cpp | 1 - core/src/kmx/kmx_environment.cpp | 1 - core/src/kmx/kmx_file.cpp | 1 - core/src/kmx/kmx_modifiers.cpp | 1 - core/src/kmx/kmx_options.cpp | 1 - core/src/kmx/kmx_processevent.cpp | 1 - core/src/kmx/kmx_processor.cpp | 1 - core/src/kmx/kmx_xstring.cpp | 1 - core/tests/unit/kmnkbd/debug_api.tests.cpp | 1 - core/tests/unit/kmnkbd/debug_items.hpp | 1 - core/tests/unit/kmnkbd/kmx_context.tests.cpp | 1 - core/tests/unit/kmnkbd/kmx_xstring.tests.cpp | 1 - .../unit/kmx/kmx_external_event.tests.cpp | 1 - core/tests/unit/kmx/kmx_imx.tests.cpp | 1 - core/tests/unit/kmx/kmx_key_list.tests.cpp | 1 - core/tests/unit/ldml/kmx_plus.tests.cpp | 1 - core/tests/unit/ldml/transforms.tests.cpp | 1 - developer/src/kmanalyze/kmanalyze.cpp | 2 - .../test/data/Test_C0_C1_C2_C3_2.keylayout | 58 +++++++++++++++++++ developer/src/kmcmplib/src/Compiler.cpp | 1 - developer/src/kmdecomp/savekeyboard.cpp | 2 - 26 files changed, 58 insertions(+), 32 deletions(-) create mode 100644 developer/src/kmc-convert/test/data/Test_C0_C1_C2_C3_2.keylayout diff --git a/common/include/km_vkey.h b/common/include/km_vkey.h index 438a283b51..78096be75b 100644 --- a/common/include/km_vkey.h +++ b/common/include/km_vkey.h @@ -7,9 +7,6 @@ #pragma once -namespace km { -namespace vkey { - #define VK_LBUTTON 0x01 #define VK_RBUTTON 0x02 #define VK_CANCEL 0x03 @@ -156,6 +153,3 @@ namespace vkey { #define VK_OEM_8 0xDF #define VK_OEM_102 0xE2 #define VK_OEM_CLEAR 0xFE - -} // namespace vkey -} // namespace km diff --git a/core/src/kmx/kmx_actions.cpp b/core/src/kmx/kmx_actions.cpp index 5b4eabe713..5ea5f4e8a1 100644 --- a/core/src/kmx/kmx_actions.cpp +++ b/core/src/kmx/kmx_actions.cpp @@ -6,7 +6,6 @@ using namespace km::core; using namespace kmx; -using namespace km::vkey; void KMX_Actions::ResetQueue() { diff --git a/core/src/kmx/kmx_capslock.cpp b/core/src/kmx/kmx_capslock.cpp index 4188541171..c304f4ddd3 100644 --- a/core/src/kmx/kmx_capslock.cpp +++ b/core/src/kmx/kmx_capslock.cpp @@ -7,7 +7,6 @@ using namespace km::core; using namespace kmx; -using namespace km::vkey; /** * Set caps lock state. diff --git a/core/src/kmx/kmx_context.cpp b/core/src/kmx/kmx_context.cpp index 9c3822a04e..2a0a0ab614 100644 --- a/core/src/kmx/kmx_context.cpp +++ b/core/src/kmx/kmx_context.cpp @@ -6,7 +6,6 @@ using namespace km::core; using namespace kmx; -using namespace km::vkey; /* KMX_Context */ diff --git a/core/src/kmx/kmx_conversion.cpp b/core/src/kmx/kmx_conversion.cpp index dacaaef97b..a3367b0414 100644 --- a/core/src/kmx/kmx_conversion.cpp +++ b/core/src/kmx/kmx_conversion.cpp @@ -5,7 +5,6 @@ using namespace km::core; using namespace kmx; -using namespace km::vkey; /* This array is lifted from preservedkeymap.cpp */ diff --git a/core/src/kmx/kmx_debugger.cpp b/core/src/kmx/kmx_debugger.cpp index 00261de5f4..f380e28942 100644 --- a/core/src/kmx/kmx_debugger.cpp +++ b/core/src/kmx/kmx_debugger.cpp @@ -10,7 +10,6 @@ using namespace km::core; using namespace kmx; -using namespace km::vkey; void KMX_DebugItems::push_item( uint8_t type, diff --git a/core/src/kmx/kmx_environment.cpp b/core/src/kmx/kmx_environment.cpp index 8216791f1a..bd4d57d0e7 100644 --- a/core/src/kmx/kmx_environment.cpp +++ b/core/src/kmx/kmx_environment.cpp @@ -9,7 +9,6 @@ using namespace km::core; using namespace kmx; -using namespace km::vkey; namespace { km_core_cu const diff --git a/core/src/kmx/kmx_file.cpp b/core/src/kmx/kmx_file.cpp index a6dceb755a..ab90df7d52 100644 --- a/core/src/kmx/kmx_file.cpp +++ b/core/src/kmx/kmx_file.cpp @@ -8,7 +8,6 @@ using namespace km::core; using namespace kmx; -using namespace km::vkey; #if defined(_WIN32) || defined(_WIN64) #include diff --git a/core/src/kmx/kmx_modifiers.cpp b/core/src/kmx/kmx_modifiers.cpp index dcf27a9050..a0173c9cdc 100644 --- a/core/src/kmx/kmx_modifiers.cpp +++ b/core/src/kmx/kmx_modifiers.cpp @@ -6,7 +6,6 @@ using namespace km::core; using namespace kmx; -using namespace km::vkey; #define MAX_RSHIFT 24 #define MAX_KSHIFT 18 diff --git a/core/src/kmx/kmx_options.cpp b/core/src/kmx/kmx_options.cpp index 55f306e0dc..6d844d1874 100644 --- a/core/src/kmx/kmx_options.cpp +++ b/core/src/kmx/kmx_options.cpp @@ -8,7 +8,6 @@ using namespace km::core; using namespace kmx; -using namespace km::vkey; int KMX_Options::_GetIndex(std::u16string const &key) const { auto i = 0U; diff --git a/core/src/kmx/kmx_processevent.cpp b/core/src/kmx/kmx_processevent.cpp index e7dde1b00e..09ebcdd4cd 100644 --- a/core/src/kmx/kmx_processevent.cpp +++ b/core/src/kmx/kmx_processevent.cpp @@ -8,7 +8,6 @@ using namespace km::core; using namespace kmx; -using namespace km::vkey; /* Globals */ diff --git a/core/src/kmx/kmx_processor.cpp b/core/src/kmx/kmx_processor.cpp index 42d2d8dd4e..b4280384ca 100644 --- a/core/src/kmx/kmx_processor.cpp +++ b/core/src/kmx/kmx_processor.cpp @@ -5,7 +5,6 @@ using namespace km::core; using namespace kmx; -using namespace km::vkey; static KMX_BOOL ContextItemsFromAppContext(KMX_WCHAR *buf, km_core_context_item** outPtr) { diff --git a/core/src/kmx/kmx_xstring.cpp b/core/src/kmx/kmx_xstring.cpp index c7c05010f7..794ba26889 100644 --- a/core/src/kmx/kmx_xstring.cpp +++ b/core/src/kmx/kmx_xstring.cpp @@ -10,7 +10,6 @@ using namespace km::core; using namespace kmx; -using namespace km::vkey; const km_core_cu *km::core::kmx::u16chr(const km_core_cu *p, km_core_cu ch) { while (*p) { diff --git a/core/tests/unit/kmnkbd/debug_api.tests.cpp b/core/tests/unit/kmnkbd/debug_api.tests.cpp index a7239902eb..6cd04200f6 100644 --- a/core/tests/unit/kmnkbd/debug_api.tests.cpp +++ b/core/tests/unit/kmnkbd/debug_api.tests.cpp @@ -25,7 +25,6 @@ #include "../load_kmx_file.hpp" using namespace km::core::kmx; -using namespace km::vkey; km_core_option_item test_env_opts[] = { diff --git a/core/tests/unit/kmnkbd/debug_items.hpp b/core/tests/unit/kmnkbd/debug_items.hpp index 41e6e6dfd5..9e281631f6 100644 --- a/core/tests/unit/kmnkbd/debug_items.hpp +++ b/core/tests/unit/kmnkbd/debug_items.hpp @@ -6,7 +6,6 @@ #include using namespace km::core::kmx; -using namespace km::vkey; namespace { diff --git a/core/tests/unit/kmnkbd/kmx_context.tests.cpp b/core/tests/unit/kmnkbd/kmx_context.tests.cpp index 5cd9902038..0679c34d1a 100644 --- a/core/tests/unit/kmnkbd/kmx_context.tests.cpp +++ b/core/tests/unit/kmnkbd/kmx_context.tests.cpp @@ -16,7 +16,6 @@ #include using namespace km::core::kmx; -using namespace km::vkey; using namespace std; void diff --git a/core/tests/unit/kmnkbd/kmx_xstring.tests.cpp b/core/tests/unit/kmnkbd/kmx_xstring.tests.cpp index d909a709ca..529e230810 100644 --- a/core/tests/unit/kmnkbd/kmx_xstring.tests.cpp +++ b/core/tests/unit/kmnkbd/kmx_xstring.tests.cpp @@ -22,7 +22,6 @@ #include using namespace km::core::kmx; -using namespace km::vkey; using namespace std; #define U_1F609_WINKING_FACE u"\U0001F609" diff --git a/core/tests/unit/kmx/kmx_external_event.tests.cpp b/core/tests/unit/kmx/kmx_external_event.tests.cpp index 43e2b4501a..15f2d284df 100644 --- a/core/tests/unit/kmx/kmx_external_event.tests.cpp +++ b/core/tests/unit/kmx/kmx_external_event.tests.cpp @@ -26,7 +26,6 @@ */ using namespace km::core::kmx; -using namespace km::vkey; km_core_option_item test_env_opts[] = { diff --git a/core/tests/unit/kmx/kmx_imx.tests.cpp b/core/tests/unit/kmx/kmx_imx.tests.cpp index e604126848..ab2bbb372b 100644 --- a/core/tests/unit/kmx/kmx_imx.tests.cpp +++ b/core/tests/unit/kmx/kmx_imx.tests.cpp @@ -27,7 +27,6 @@ */ using namespace km::core::kmx; -using namespace km::vkey; km_core_option_item test_env_opts[] = { diff --git a/core/tests/unit/kmx/kmx_key_list.tests.cpp b/core/tests/unit/kmx/kmx_key_list.tests.cpp index dea880c69d..7b811bd0b1 100644 --- a/core/tests/unit/kmx/kmx_key_list.tests.cpp +++ b/core/tests/unit/kmx/kmx_key_list.tests.cpp @@ -18,7 +18,6 @@ #include "../load_kmx_file.hpp" using namespace km::core::kmx; -using namespace km::vkey; km_core_option_item test_env_opts[] = { diff --git a/core/tests/unit/ldml/kmx_plus.tests.cpp b/core/tests/unit/ldml/kmx_plus.tests.cpp index 592e697757..c0643a5d60 100644 --- a/core/tests/unit/ldml/kmx_plus.tests.cpp +++ b/core/tests/unit/ldml/kmx_plus.tests.cpp @@ -11,7 +11,6 @@ #include "utfcodec.hpp" using namespace km::core::kmx; -using namespace km::vkey; TEST(KMXPlusTest, test_COMP_KMXPLUS_KEYS_KEY) { COMP_KMXPLUS_KEYS_KEY e[2] = { diff --git a/core/tests/unit/ldml/transforms.tests.cpp b/core/tests/unit/ldml/transforms.tests.cpp index fcd9591ccf..411d941047 100644 --- a/core/tests/unit/ldml/transforms.tests.cpp +++ b/core/tests/unit/ldml/transforms.tests.cpp @@ -42,7 +42,6 @@ using namespace km::core::ldml; using namespace km::core::kmx; -using namespace km::vkey; void prepend_hex_oct(std::u32string &str, char32_t x) { diff --git a/developer/src/kmanalyze/kmanalyze.cpp b/developer/src/kmanalyze/kmanalyze.cpp index ef3009a348..fb12cae1be 100644 --- a/developer/src/kmanalyze/kmanalyze.cpp +++ b/developer/src/kmanalyze/kmanalyze.cpp @@ -9,8 +9,6 @@ #include #include "../../../common/include/km_vkey.h" -using namespace km::vkey; - BOOL LoadKeyboard(LPSTR fileName, LPKEYBOARD *lpKeyboard); void Err(const char *p); diff --git a/developer/src/kmc-convert/test/data/Test_C0_C1_C2_C3_2.keylayout b/developer/src/kmc-convert/test/data/Test_C0_C1_C2_C3_2.keylayout new file mode 100644 index 0000000000..35c3743eb5 --- /dev/null +++ b/developer/src/kmc-convert/test/data/Test_C0_C1_C2_C3_2.keylayout @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/developer/src/kmcmplib/src/Compiler.cpp b/developer/src/kmcmplib/src/Compiler.cpp index d1e0d766ba..d9f3c5c9cb 100644 --- a/developer/src/kmcmplib/src/Compiler.cpp +++ b/developer/src/kmcmplib/src/Compiler.cpp @@ -110,7 +110,6 @@ #define SUBLANGID(lgid) ((uint16_t)(lgid) >> 10) using namespace kmcmp; -using namespace km::vkey; KMX_BOOL AWarnDeprecatedCode_GLOBAL_LIB; diff --git a/developer/src/kmdecomp/savekeyboard.cpp b/developer/src/kmdecomp/savekeyboard.cpp index 1160ef0c1b..bfe66e5bc6 100644 --- a/developer/src/kmdecomp/savekeyboard.cpp +++ b/developer/src/kmdecomp/savekeyboard.cpp @@ -35,8 +35,6 @@ #include "../../../common/include/vkeys.h" #include "../../../common/include/km_vkey.h" -using namespace km::vkey; - #define SSN__PREFIX L"&" const char *UTF16Sig = "\xFF\xFE"; From f1361a94f086b4562e5e748f86b7ea45e68ab641 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 12 Aug 2026 11:07:31 +0200 Subject: [PATCH 126/140] feat(web): add `keyman.versionInfo` API KeymanWeb has long had `keyman.build` and `keyman.version`. Neither of these properties expose the full version string, and consumers are forced to manipulate them to get a partial version string. This change deprecates both these properties, and introduces a new `keyman.versionInfo` property, which returns an object with all the version information, most importantly `full`. Update documentation and add a unit test for this, following the unit test pattern established throughout Keyman of a unit test filename matching the implementation class. The `KeymanEngineBase` class is a little difficult to instantiate so this unit test module currently has a minimal instantiation which provides enough to fulfill the one included test. Test-bot: skip --- web/docs/engine/reference/core/build.md | 23 ++++-- web/docs/engine/reference/core/index.md | 28 ++++--- web/docs/engine/reference/core/version.md | 19 +++-- web/docs/engine/reference/core/versionInfo.md | 81 +++++++++++++++++++ web/src/engine/src/main/keymanEngineBase.ts | 34 ++++++++ .../engine/main/keymanEngineBase.tests.ts | 65 +++++++++++++++ 6 files changed, 226 insertions(+), 24 deletions(-) create mode 100644 web/docs/engine/reference/core/versionInfo.md create mode 100644 web/src/test/auto/headless/engine/main/keymanEngineBase.tests.ts diff --git a/web/docs/engine/reference/core/build.md b/web/docs/engine/reference/core/build.md index 75824dd48d..04cde69b50 100644 --- a/web/docs/engine/reference/core/build.md +++ b/web/docs/engine/reference/core/build.md @@ -1,20 +1,20 @@ --- -title: build +title: build (deprecated) --- ## Summary -Denotes the release-version .patch component for the loaded KeymanWeb instance. +Returns the patch component of the KeymanWeb version (major.minor.patch) as a number. ## Syntax -```c +```js keyman.build ``` -## Type +### Type -string +number ### Access @@ -22,8 +22,17 @@ Read only ### Return Value -(for example) `14.0.1` +The patch component of the version, e.g. if the version is `"18.0.249"`, then returns `249`. ## Description -... +This function is deprecated; to get the full KeymanWeb version string, use [`keyman.versionInfo`](versionInfo). + +## History + +19.0: deprecated + +## See also + +* [`keyman.version`](version) +* [`keyman.versionInfo`](versionInfo) diff --git a/web/docs/engine/reference/core/index.md b/web/docs/engine/reference/core/index.md index ff474d539b..18b6b1db3f 100644 --- a/web/docs/engine/reference/core/index.md +++ b/web/docs/engine/reference/core/index.md @@ -4,18 +4,6 @@ title: `keyman` - Core Module The KeymanWeb core module is exposed to the developer as `window.keyman`. -[`build` Property](build) -: The release build of KeymanWeb. - - -[`version` Property](version) -: The version of KeymanWeb. - - -[`initialized` Property](initialized) -: Keymanweb core module initialization state. - - [`activatingUI` Function](activatingUI) : Set an internal flag to notify KeymanWeb of change in UI activation state. @@ -40,6 +28,10 @@ The KeymanWeb core module is exposed to the developer as `window.keyman`. : Attach KeymanWeb to HTML element (or IFrame). +[`build` Property](build) (deprecated) +: The patch component for the current version of KeymanWeb. + + [ `BuildVisualKeyboard` Function](BuildVisualKeyboard) : Create a copy of the OSK for embedding in documentation or help page. @@ -97,6 +89,10 @@ The KeymanWeb core module is exposed to the developer as `window.keyman`. : Sets license key, selects user interface, and other KeymanWeb Options. +[`initialized` Property](initialized) +: Keymanweb core module initialization state. + + [`isChiral` Function](isChiral) : Test if a given keyboard recognizes chiral modifier data, such as left-control vs right-control. @@ -131,3 +127,11 @@ The KeymanWeb core module is exposed to the developer as `window.keyman`. [`setKeyboardForControl` Function](setKeyboardForControl) : Associate control with independent keyboard settings initialized to a specific keyboard. + + +[`version` Property](version) (deprecated) +: The major and minor components of the version of KeymanWeb. + + +[`versionInfo` Property](versionInfo) +: Full version info for KeymanWeb. diff --git a/web/docs/engine/reference/core/version.md b/web/docs/engine/reference/core/version.md index 40b9db4529..beaa7a7f6e 100644 --- a/web/docs/engine/reference/core/version.md +++ b/web/docs/engine/reference/core/version.md @@ -1,14 +1,14 @@ --- -title: version +title: version (deprecated) --- ## Summary -The version number of the loaded KeymanWeb instance. +Returns the major and minor components of the KeymanWeb version (major.minor.patch) as a string. ## Syntax -```c +```js keyman.version ``` @@ -22,8 +22,17 @@ Read only ### Return Value -`'19.0'` (for KeymanWeb 19.0) +The major and minor components of the version, e.g. if the version is `"18.0.249"`, then returns `"18.0"`. ## Description -... +This function is deprecated; to get the full KeymanWeb version string, use [`keyman.versionInfo`](versionInfo). + +## History + +19.0: deprecated + +## See also + +* [`keyman.build`](build) +* [`keyman.versionInfo`](versionInfo) diff --git a/web/docs/engine/reference/core/versionInfo.md b/web/docs/engine/reference/core/versionInfo.md new file mode 100644 index 0000000000..c1a2769144 --- /dev/null +++ b/web/docs/engine/reference/core/versionInfo.md @@ -0,0 +1,81 @@ +--- +title: versionInfo +--- + +## Summary + +Returns an object describing the current KeymanWeb version. + +## Syntax + +```js +keyman.versionInfo +``` + +### Type + +`object` + +### Access + +Read only + +### Return Value + +An object with the following properties: + +* `full: string` +: `"major.minor.patch[-tier][-environment]"`. `tier` will be included for alpha + and beta. `environment` will be included for local or test builds, and for + test builds may include an additional test context such as a pull request + number. + +* `major: number` +: major component of the version string + +* `minor: number` +: minor component of the version string + +* `patch: number` +: patch component of the version string + +* `version: string` +: `"major.minor.patch"` string + +* `tier: string` +: The tier, `"stable"`, `"beta"`, or `"alpha"` + +* `environment: string` +: One of `"stable"`, `"beta"`, `"alpha"`, `"local"`, or `"test"`. For release + builds, the same as `tier`; for development builds, `"local"`, and for test + builds `"test"`. Test context is only included in the `full` property. + +## Example return value + +```js +{ + full: "18.0.249-alpha-local", + major: 18, + minor: 0, + patch: 249, + version: "18.0.249", + tier: "alpha", + environment: "local" +} +``` + +## Description + +This property should be used instead of the deprecated [`keyman.build`] or +[`keyman.version`] properties. In most cases, you should use the `full` property +as the version to display to users. The `major` property can be used for version +gating. + +## History + +* 19.0: introduced `versionInfo` property, deprecated `build` and `version`. + +## See also + +* [`keyman.build`](build) +* [`keyman.version`](version) diff --git a/web/src/engine/src/main/keymanEngineBase.ts b/web/src/engine/src/main/keymanEngineBase.ts index 474102dfe5..ab1534b47c 100644 --- a/web/src/engine/src/main/keymanEngineBase.ts +++ b/web/src/engine/src/main/keymanEngineBase.ts @@ -1,3 +1,6 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + */ import { type KeyEvent, JSKeyboard, Keyboard, KeyboardProperties, KeyboardKeymanGlobal, ProcessorAction } from "keyman/engine/keyboard"; import { ProcessorInitOptions } from 'keyman/engine/js-processor'; import { DOMKeyboardLoader } from "keyman/engine/keyboard"; @@ -339,6 +342,8 @@ export class KeymanEngineBase< /** * Public API: Denotes the 'patch' component of the version of the current engine. * + * 19.0: deprecated + * * https://help.keyman.com/developer/engine/web/current-version/reference/core/build */ public get build(): number { @@ -348,12 +353,41 @@ export class KeymanEngineBase< /** * Public API: Denotes the major & minor components of the version of the current engine. * + * 19.0: deprecated + * * https://help.keyman.com/developer/engine/web/current-version/reference/core/version */ public get version(): string { return KEYMAN_VERSION.VERSION_RELEASE; } + /** + * Public API: Returns version information for the current engine + * + * 19.0: introduced, replacing build and version properties + * + * https://help.keyman.com/developer/engine/web/current-version/reference/core/versionInfo + */ + public get versionInfo(): { + full: string; + major: number; + minor: number; + patch: number; + version: string; + tier: string; + environment: string; + } { + return { + full: KEYMAN_VERSION.VERSION_WITH_TAG, + major: parseInt(KEYMAN_VERSION.VERSION_MAJOR, 10), + minor: parseInt(KEYMAN_VERSION.VERSION_MINOR, 10), + patch: parseInt(KEYMAN_VERSION.VERSION_PATCH, 10), + version: KEYMAN_VERSION.VERSION, + tier: KEYMAN_VERSION.TIER, + environment: KEYMAN_VERSION.VERSION_ENVIRONMENT, + } + } + public get hardKeyboard(): HardKeyboardT { return this._hardKeyboard; } diff --git a/web/src/test/auto/headless/engine/main/keymanEngineBase.tests.ts b/web/src/test/auto/headless/engine/main/keymanEngineBase.tests.ts new file mode 100644 index 0000000000..fb89f6acac --- /dev/null +++ b/web/src/test/auto/headless/engine/main/keymanEngineBase.tests.ts @@ -0,0 +1,65 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + * + * Created by mcdurdin on 2026-08-12 + * + * basic unit tests for KeymanEngineBase class + */ +import { assert } from 'chai'; +import { ContextManagerBase, EngineConfiguration, KeyboardInfoPair, KeymanEngineBase } from 'keyman/engine/main'; +import KEYMAN_VERSION from '@keymanapp/keyman-version'; +import { Keyboard, TextStore } from 'keyman/engine/keyboard'; +import { KeyboardStub } from 'keyman/engine/keyboard-storage'; +import { DeviceSpec } from 'keyman/common/web-utils'; + +class ContextManagerTest extends ContextManagerBase { + initialize(): void {} + get activeTextStore(): TextStore { return null; } + get activeKeyboard(): {keyboard: Keyboard, metadata: KeyboardStub} { return null }; + protected currentKeyboardSrcTextStore(): TextStore | null { return null; } + protected activateKeyboardForTextStore(kbd: KeyboardInfoPair, textStore: TextStore): void {} + protected getFallbackStubKey(): { id: string, langId: string } { return null; } +} + +/** + * construct a stubbed KeymanEngineBase suitable for unit testing + */ +function instantiate() { + const device = new DeviceSpec(DeviceSpec.Browser.Chrome, DeviceSpec.FormFactor.Desktop, DeviceSpec.OperatingSystem.Windows, false); + const config = new EngineConfiguration('', device); + const keyman = new KeymanEngineBase(null, config, new ContextManagerTest(config), (engine) => ({ + baseLayout: 'us', + keyboardInterface: null, // new KeyboardInterfaceBase(window, engine, config.stubNamespacer), + defaultOutputRules: null //new DefaultOutputRules() + })); + return keyman; +} + +describe('KeymanEngineBase', function () { + it('returns expected version information', async function () { + const keyman = instantiate(); + assert.isNumber(keyman.build); + assert.equal(keyman.build, parseInt(KEYMAN_VERSION.VERSION_PATCH, 10)); + + assert.isString(keyman.version); + assert.equal(keyman.version, KEYMAN_VERSION.VERSION_RELEASE); + + assert.oneOf(keyman.versionInfo.environment, ['alpha', 'beta', 'test', 'local']); + assert.equal(keyman.versionInfo.environment, KEYMAN_VERSION.VERSION_ENVIRONMENT); + + assert.isNumber(keyman.versionInfo.major); + assert.equal(keyman.versionInfo.major, parseInt(KEYMAN_VERSION.VERSION_MAJOR, 10)); + + assert.isNumber(keyman.versionInfo.minor); + assert.equal(keyman.versionInfo.minor, parseInt(KEYMAN_VERSION.VERSION_MINOR, 10)); + + assert.isNumber(keyman.versionInfo.patch); + assert.equal(keyman.versionInfo.patch, parseInt(KEYMAN_VERSION.VERSION_PATCH, 10)); + + assert.oneOf(keyman.versionInfo.tier, ['alpha', 'beta', 'stable']); + assert.equal(keyman.versionInfo.tier, KEYMAN_VERSION.TIER); + + assert.match(keyman.versionInfo.version, /^\d+\.\d+\.\d+$/); + assert.equal(keyman.versionInfo.version, KEYMAN_VERSION.VERSION); + }); +}); \ No newline at end of file From b40c4b376b0cd15c1d08269a9ce641e554666a55 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 12 Aug 2026 15:51:55 +0200 Subject: [PATCH 127/140] fix(web): cleanup `loaduserinterface` and `unloaduserinterface` events The `loaduserinterface` and `unloaduserinterface` events were incompletely implemented, and the documentation was incomplete. This change: 1. ensures `loaduserinterface` is called any time a UI is set, either during initialization or when the property is set later. 2. ensures `unloaduserinterface` is called immediately before a UI is shutdown. 3. updates documentation 4. removes misleading commented code in kmwuifloat.ts Test-bot: skip --- .../reference/events/keyman/loaduserinterface.md | 13 ++++++++----- .../reference/events/keyman/unloaduserinterface.md | 13 ++++++++----- web/src/app/browser/src/keymanEngine.ts | 8 ++++---- web/src/app/ui/kmwuifloat.ts | 9 +-------- 4 files changed, 21 insertions(+), 22 deletions(-) diff --git a/web/docs/engine/reference/events/keyman/loaduserinterface.md b/web/docs/engine/reference/events/keyman/loaduserinterface.md index 8893b45fd1..9138762a91 100644 --- a/web/docs/engine/reference/events/keyman/loaduserinterface.md +++ b/web/docs/engine/reference/events/keyman/loaduserinterface.md @@ -4,11 +4,11 @@ title: keyman.loaduserinterface Event ## Summary -Called to initiate the ui initialization process. +Called after a KeymanWeb UI has been loaded and initialized. ## Syntax -```javascript +```js keyman.addEventListener('loaduserinterface', function() { ... }); @@ -21,10 +21,13 @@ None. ### Return Value `boolean` -: `true` if the event should continue processing, `false` if it should - not. Your event handler should return `true` aside from exceptional +: `true` if the next event handler should be called, `false` if it should not. + Your event handler should return `true` aside from exceptional circumstances. ## Description -... +If no UI module has been loaded, then this event will not be called. If a UI is +loaded by script before KeymanWeb, it will be initialized at the end of +KeymanWeb's initialization sequence, but before the init promise resolves and +before [`initialized`](../../core/initialized.md) is set to `2`. diff --git a/web/docs/engine/reference/events/keyman/unloaduserinterface.md b/web/docs/engine/reference/events/keyman/unloaduserinterface.md index 88a9ca9064..d1ca590c16 100644 --- a/web/docs/engine/reference/events/keyman/unloaduserinterface.md +++ b/web/docs/engine/reference/events/keyman/unloaduserinterface.md @@ -4,11 +4,11 @@ title: keyman.unloaduserinterface Event ## Summary -Called when allow ui clean-up. +Called when a KeymanWeb UI is about to be unloaded. ## Syntax -```javascript +```js keyman.addEventListener('unloaduserinterface', function() { ... }); @@ -21,11 +21,14 @@ None. ### Return Value `boolean` -: `true` if the event should continue processing, `false` if it should - not. Your event handler should return `true` aside from exceptional +: `true` if the next event handler should be called, `false` if it should not. + Your event handler should return `true` aside from exceptional circumstances. ## Description -Called when the ui is to be unloaded, allowing cleanup of resources if +Called when the ui is about to be unloaded, allowing cleanup of resources if necessary. + +It is not possible to stop the UI being unloaded; returning `false` only stops +further event handlers from being called. diff --git a/web/src/app/browser/src/keymanEngine.ts b/web/src/app/browser/src/keymanEngine.ts index 0b3d54aebb..5630ee2375 100644 --- a/web/src/app/browser/src/keymanEngine.ts +++ b/web/src/app/browser/src/keymanEngine.ts @@ -131,12 +131,14 @@ export class KeymanEngine extends KeymanEngineBase { - this.KeyboardSelector = this.innerDiv = this.outerDiv = this.kbdIcon = null; - }; - /** * UI removal - resource cleanup */ @@ -215,7 +211,7 @@ if(!keyman) { root.parentNode.removeChild(root); } - this._UnloadUserInterface(); + this.KeyboardSelector = this.innerDiv = this.outerDiv = this.kbdIcon = null; if(window.removeEventListener) { window.removeEventListener('resize', this._Resize, false); @@ -607,9 +603,6 @@ if(!keyman) { // but also call initialization when script loaded, which is after KMW initialization for asynchronous script loading ui.initialize(); - // is/was never actually raised. Note that the `shutdown` method likely fulfills a similar role. - // keyman.addEventListener('unloaduserinterface', ui._UnloadUserInterface); - } catch(err){} // Do not wrap in an anonymous function - let the closure compiler do that, but From 1f82507730846f555468d1526f0d1e47e8ac049b Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 12 Aug 2026 16:07:22 +0200 Subject: [PATCH 128/140] docs(web): deprecate unused and obsolete API endpoints and docs cleanup Deprecate the following obsolete functions and properties: * `keyman.helpURL` * `keyman.util.rgba` * `keyman.util.toFloat` * `keyman.util.toNumber` * `keyman.util.toNzString` Use correct language for code blocks in Keyman Engine for Web documentation. Test-bot: skip --- web/docs/engine/guide/adding-keyboards.md | 10 ++++----- .../guide/examples/control-by-control.md | 2 +- .../guide/examples/full-manual-control.md | 2 +- .../engine/guide/examples/manual-control.md | 2 +- .../reference/core/BuildVisualKeyboard.md | 2 +- .../engine/reference/core/activatingUI.md | 2 +- .../engine/reference/core/addEventListener.md | 4 ++-- web/docs/engine/reference/core/addHotKey.md | 2 +- .../engine/reference/core/attachToControl.md | 2 +- .../reference/core/detachFromControl.md | 2 +- .../engine/reference/core/disableControl.md | 2 +- .../engine/reference/core/enableControl.md | 4 ++-- .../reference/core/focusLastActiveElement.md | 7 +++--- .../reference/core/getActiveKeyboard.md | 2 +- .../reference/core/getActiveLanguage.md | 22 ++++++++++++++----- .../reference/core/getKeyboardForControl.md | 2 +- .../reference/core/getLastActiveElement.md | 7 +++--- .../engine/reference/core/getSavedKeyboard.md | 2 +- web/docs/engine/reference/core/getUIState.md | 2 +- web/docs/engine/reference/core/helpURL.md | 16 +++++++++----- web/docs/engine/reference/core/index.md | 4 ++++ web/docs/engine/reference/core/initialized.md | 8 +++---- web/docs/engine/reference/core/isCJK.md | 2 +- web/docs/engine/reference/core/isChiral.md | 2 +- .../engine/reference/core/moveToElement.md | 2 +- .../reference/core/removeEventListener.md | 2 +- .../engine/reference/core/removeHotKey.md | 2 +- .../engine/reference/core/removeKeyboards.md | 2 +- .../engine/reference/core/resetContext.md | 11 +++++++--- .../reference/core/setActiveKeyboard.md | 2 +- .../reference/core/setKeyboardForControl.md | 2 +- web/docs/engine/reference/events/index.md | 4 ++-- .../events/keyman/beforekeyboardchange.md | 2 +- .../reference/events/keyman/controlblurred.md | 2 +- .../reference/events/keyman/controlfocused.md | 2 +- .../reference/events/keyman/keyboardchange.md | 2 +- .../reference/events/keyman/keyboardloaded.md | 2 +- .../events/keyman/keyboardregistered.md | 2 +- .../reference/events/osk/configclick.md | 4 ++-- .../engine/reference/events/osk/helpclick.md | 4 ++-- web/docs/engine/reference/events/osk/hide.md | 8 +++---- .../engine/reference/events/osk/resizemove.md | 4 ++-- web/docs/engine/reference/events/osk/show.md | 9 ++++---- web/docs/engine/reference/interface/_nul.md | 4 ++-- web/docs/engine/reference/interface/any.md | 6 ++--- web/docs/engine/reference/interface/beep.md | 4 ++-- .../engine/reference/interface/beepReset.md | 4 ++-- .../engine/reference/interface/context.md | 6 ++--- .../reference/interface/contextMatch.md | 6 ++--- .../reference/interface/deadkeyMatch.md | 8 +++---- .../reference/interface/deadkeyOutput.md | 6 ++--- .../reference/interface/deleteContext.md | 6 ++--- .../reference/interface/fullContextMatch.md | 6 ++--- .../engine/reference/interface/ifStore.md | 4 ++-- web/docs/engine/reference/interface/index.md | 18 +++++++-------- .../engine/reference/interface/indexOutput.md | 6 ++--- .../engine/reference/interface/insertText.md | 4 ++-- .../engine/reference/interface/isKeypress.md | 4 ++-- .../reference/interface/keyInformation.md | 4 ++-- .../engine/reference/interface/keyMatch.md | 6 ++--- .../engine/reference/interface/loadStore.md | 4 ++-- web/docs/engine/reference/interface/output.md | 6 ++--- .../reference/interface/registerKeyboard.md | 4 ++-- .../engine/reference/interface/saveFocus.md | 4 ++-- .../engine/reference/interface/saveStore.md | 4 ++-- .../engine/reference/interface/setStore.md | 4 ++-- .../engine/reference/interface/stateMatch.md | 8 +++---- .../engine/reference/osk/addEventListener.md | 2 +- web/docs/engine/reference/osk/getRect.md | 2 +- web/docs/engine/reference/osk/hide.md | 2 +- web/docs/engine/reference/osk/isEnabled.md | 2 +- web/docs/engine/reference/osk/isVisible.md | 2 +- .../reference/osk/removeEventListener.md | 4 ++-- web/docs/engine/reference/osk/setPos.md | 2 +- web/docs/engine/reference/osk/setRect.md | 2 +- web/docs/engine/reference/osk/show.md | 2 +- web/docs/engine/reference/osk/userLocated.md | 2 +- .../engine/reference/util/addStyleSheet.md | 2 +- web/docs/engine/reference/util/alert.md | 2 +- .../engine/reference/util/attachDOMEvent.md | 2 +- .../engine/reference/util/createElement.md | 2 +- .../engine/reference/util/detachDOMEvent.md | 2 +- web/docs/engine/reference/util/getAbsolute.md | 4 ++-- .../engine/reference/util/getAbsoluteX.md | 4 ++-- .../engine/reference/util/getAbsoluteY.md | 4 ++-- web/docs/engine/reference/util/getOption.md | 2 +- web/docs/engine/reference/util/index.md | 8 +++---- .../engine/reference/util/isTouchDevice.md | 8 ++----- .../engine/reference/util/linkStyleSheet.md | 2 +- web/docs/engine/reference/util/loadCookie.md | 8 +++---- .../engine/reference/util/removeStyleSheet.md | 2 +- web/docs/engine/reference/util/rgba.md | 15 ++++++++++--- web/docs/engine/reference/util/saveCookie.md | 7 +++--- web/docs/engine/reference/util/setOption.md | 2 +- web/docs/engine/reference/util/toFloat.md | 13 ++++++++--- web/docs/engine/reference/util/toNumber.md | 14 +++++++++--- web/docs/engine/reference/util/toNzString.md | 16 +++++++++----- web/src/app/browser/src/keymanEngine.ts | 4 ++-- web/src/app/browser/src/utilApiEndpoint.ts | 12 ++++++---- 99 files changed, 266 insertions(+), 207 deletions(-) diff --git a/web/docs/engine/guide/adding-keyboards.md b/web/docs/engine/guide/adding-keyboards.md index 957df80270..f294bee24b 100644 --- a/web/docs/engine/guide/adding-keyboards.md +++ b/web/docs/engine/guide/adding-keyboards.md @@ -8,7 +8,7 @@ There are multiple ways to add and install keyboards into your KeymanWeb install The most efficient way to utilize a keyboard is to obtain a local copy of it and place this copy in a static location on your website. Once this is done, it can be directly linked into KeymanWeb as follows. -```typescript +```js await keyman.addKeyboards({ id:'us', // The keyboard's unique identification code. name:'English', // The keyboard's user-readable name. @@ -22,7 +22,7 @@ await keyman.addKeyboards({ Custom fonts may also be utilized via the `language.font` property. For example: -```typescript +```js font:{ family:'LaoWeb', source:['../font/saysettha_web.ttf','../font/saysettha_web.woff','../font/saysettha_web.eot'] @@ -33,7 +33,7 @@ font:{ To obtain the default Keyman keyboard for a given language, call the following function. -```typescript +```js await keyman.addKeyboardsForLanguage('Dzongkha'); ``` @@ -41,7 +41,7 @@ This example would find the default keyboard for the Dzongkha language. This met Alternatively, languages may be looked up via their BCP 47 language code as follows: -```typescript +```js await keyman.addKeyboards('@he'); ``` @@ -51,7 +51,7 @@ The `@` prefix indicates the use of the BCP 47 language code, which in this case To obtain a specific keyboard by name or by keyboard name and language code as a pair, see the following: -```typescript +```js await keyman.addKeyboards('french', 'sil_euro_latin@sv', 'sil_euro_latin@no'); ``` diff --git a/web/docs/engine/guide/examples/control-by-control.md b/web/docs/engine/guide/examples/control-by-control.md index b3b0b9b3a5..af41cf0cee 100644 --- a/web/docs/engine/guide/examples/control-by-control.md +++ b/web/docs/engine/guide/examples/control-by-control.md @@ -11,7 +11,7 @@ to open the test page. Include the following script in the HEAD of your page: -```js +```html