Compare commits

..

180 Commits

Author SHA1 Message Date
Patrick Goldinger
83bef3aee9 Release v0.3.4 2021-01-21 23:05:57 +01:00
Patrick Goldinger
ef075151fb Merge pull request #218 from florisboard/fix-key-precise-swipe
Fix precise character deletion behaviour
2021-01-21 18:33:16 +01:00
Patrick Goldinger
5316e46ba6 Fix precise character deletion behaviour 2021-01-21 17:35:09 +01:00
Patrick Goldinger
467fb62067 Merge pull request #187 from RickyM7/master
Trying to add Brazilian Portuguese (pt-BR) Layout
2021-01-20 22:59:53 +01:00
Patrick Goldinger
75c59366a9 Extended popups now respect country code (#188)
This allows for country specific popups like e.g. pt-BR to override
pt default configuration.
2021-01-20 22:47:28 +01:00
Patrick Goldinger
6df6e25b15 Fix double key press bugs for space and shift (#170) 2021-01-19 23:39:51 +01:00
Patrick Goldinger
81d89cad8c Fix key hint bug (#211) 2021-01-19 23:09:22 +01:00
Patrick Goldinger
99954237f7 Merge pull request #212 from florisboard/feat-add-swipe-actions
Add new swipe actions / Improve gesture pref structure
2021-01-19 21:19:33 +01:00
Patrick Goldinger
b5e56642fc Add new swipe actions / Improve gesture pref structure 2021-01-19 20:03:10 +01:00
Patrick Goldinger
343525f259 Merge pull request #206 from florisboard/fix-delete-shift-width
Fix delete and shift key width for 9+ char rows
2021-01-18 20:35:13 +01:00
Patrick Goldinger
a5adad714a Merge pull request #202 from Salamandar/bépo
Add Bépo french keyboard layout
2021-01-18 19:55:50 +01:00
Félix Piédallu
67214cbdf1 Add Bépo french keyboard layout 2021-01-18 19:39:12 +01:00
Patrick Goldinger
3a0284f355 Fix delete and shift key width for 9+ char rows 2021-01-18 18:51:32 +01:00
Patrick Goldinger
eecada3118 Fix group name input not accepting colons 2021-01-18 17:59:02 +01:00
Patrick Goldinger
cdc8635793 Release v0.3.3 2021-01-17 22:56:24 +01:00
Patrick Goldinger
0ed3e7bd22 Update translations from Crowdin 2021-01-17 22:32:03 +01:00
Patrick Goldinger
18ac2dc0d8 Update CONTRIBUTING.md content adding info 2021-01-17 21:48:10 +01:00
Patrick Goldinger
c04ca29421 Merge pull request #198 from klausweiss/feature/pl-layout
Add extended popups for Polish
2021-01-17 19:54:34 +01:00
Patrick Goldinger
b6466daebb Merge branch 'master' into feature/pl-layout 2021-01-17 19:51:00 +01:00
Patrick Goldinger
d9dedc447f Merge pull request #184 from bertin0/romanian_layout
Add romanian layout, based on QWERTY (closes issue #121)
2021-01-17 19:21:55 +01:00
Patrick Goldinger
5dbd98ae9e Merge branch 'master' into romanian_layout 2021-01-17 19:18:49 +01:00
Patrick Goldinger
f8db145a63 Merge pull request #171 from tsiflimagas/greek_layout
Add Greek layout
2021-01-17 19:16:29 +01:00
Patrick Goldinger
563a24b7d1 Merge branch 'master' into greek_layout 2021-01-17 19:11:15 +01:00
Patrick Goldinger
5dbea21fab Merge pull request #167 from williamtheaker/wt.popup_character
Adds colon character to long press. Fixes #166
2021-01-17 19:01:08 +01:00
Patrick Goldinger
ce937c3f58 Merge pull request #137 from williamtheaker/wt.russian
Add Russian layout; Document adding new languages
2021-01-17 18:57:05 +01:00
Patrick Goldinger
439fdade51 Merge branch 'master' into wt.russian 2021-01-17 18:50:26 +01:00
Patrick Goldinger
a77531e483 Fix comma typo in hr.json 2021-01-17 18:47:35 +01:00
Patrick Goldinger
343646f0f8 Merge pull request #134 from HeDidNothingWrong/patch-2
Update config.json to include hr keyboard extension
2021-01-17 18:46:26 +01:00
Patrick Goldinger
91c2337633 Merge pull request #133 from HeDidNothingWrong/patch-1
Create hr.json
2021-01-17 18:45:28 +01:00
Patrick Goldinger
000c0f1e30 Merge pull request #162 from florisboard/feat-theme-rework
Theme rework (Milestone v0.4.0 / E) & Asset manager base (Milestone v0.4.0 / C)
2021-01-17 18:30:02 +01:00
Patrick Goldinger
2401b1c776 Add doc strings to some Theme classes 2021-01-17 18:26:50 +01:00
Patrick Goldinger
555e329447 Remove obsolete resources and pref declarations 2021-01-17 17:15:31 +01:00
Patrick Goldinger
a2b3033d04 Add input validation / Fix UI and logic bugs 2021-01-17 17:00:47 +01:00
Patrick Goldinger
1020fff6cd Implement create new theme fab option 2021-01-17 12:23:55 +01:00
Mikołaj Biel
6fa4fadf04 Add Polish layout 2021-01-17 10:21:03 +01:00
Patrick Goldinger
f386428acd Add license note for expandable fab library 2021-01-16 21:48:35 +01:00
Patrick Goldinger
f45540eab1 Add adaptive theme coloring 2021-01-16 21:44:33 +01:00
Patrick Goldinger
0d509f8cfb Add Theme Editor UI 2021-01-15 20:41:03 +01:00
Ricardo
68061f1aac Update pt-BR.json 2021-01-14 15:13:38 -03:00
Ricardo
c4ac4abd63 Brazilian Portuguese (pt-BR) Keyboard 2021-01-14 12:19:04 -03:00
Albert Geantă
5fd26affc9 Add romanian layout, based on QWERTY 2021-01-14 15:02:09 +02:00
tsiflimagas
198ae1fc7c Add Greek layout 2021-01-14 10:18:08 +02:00
Patrick Goldinger
1e9ce7ba54 Add basic Edit Theme UI implementation / Fix bugs 2021-01-14 01:30:27 +01:00
Willie Theaker
702785b7fc Fix incorrect character 2021-01-13 12:43:24 -08:00
Patrick Goldinger
8c37c6188b Add borderless theme presets (day & night) 2021-01-13 01:20:59 +01:00
Patrick Goldinger
69ad3e2352 Add basic Theme Manager UI 2021-01-13 01:20:30 +01:00
Willie Theaker
9161d1574d Oops misread issue 2021-01-11 14:56:14 -08:00
Willie Theaker
7ff7309e76 Adds colon character to long press. Fixes #166 2021-01-11 14:50:49 -08:00
Willie Theaker
6df803c239 Add long press characters 2021-01-11 13:15:45 -08:00
Patrick Goldinger
3385fe7cbd Add "Follow time" theme mode implementation 2021-01-11 19:57:50 +01:00
Patrick Goldinger
320b9e0751 Fix emoji keyboard view being default layout 2021-01-11 19:03:04 +01:00
Patrick Goldinger
9178207653 Improve KeyView theme attribute logic and performance
The KeyView now better caches and refreshes the theme of the key
according to its current state. Also, the Theme.getAttr() method
has been improved (both in terms of functionality and in performance).
2021-01-11 01:43:06 +01:00
Patrick Goldinger
90b0812ae4 Add basic Theme UI in Settings / Fix bugs
Does not include theme modification (yet)
2021-01-10 20:55:38 +01:00
Patrick Goldinger
fb03a82e45 Fix icon fill color not adapting to current Theme 2021-01-10 20:53:32 +01:00
Patrick Goldinger
f5a7220ba7 Adapt views to work with new theme logic 2021-01-10 13:47:52 +01:00
Patrick Goldinger
e413f3918e Add theme manager and new theme declaration 2021-01-10 13:45:51 +01:00
Patrick Goldinger
d25bdd8938 Add basic asset manager 2021-01-10 13:43:42 +01:00
Willie Theaker
1f84d08fa9 Add Russian layout; Document adding new langauges 2021-01-08 12:23:03 -08:00
HeDidNothingWrong
da9d68dd3b Update config.json
Update config for croatian extended popup
2021-01-08 18:49:21 +01:00
HeDidNothingWrong
1bc36ceec7 Update hr.json 2021-01-08 18:42:33 +01:00
HeDidNothingWrong
c57b60d00c Create hr.json
Croatian additional symbols (lowercase) - also applicable for Serbia, Slovenia, Bosnia. Casually combined with qwertz
2021-01-08 18:08:16 +01:00
Patrick Goldinger
e304fbd120 Update F-Droid info in README.md 2021-01-08 09:13:08 +01:00
Patrick Goldinger
7eb7c21e13 Merge pull request #122 from florisboard/feat-popup-layer
Rework popup UI implementation
2021-01-05 17:07:02 +01:00
Patrick Goldinger
710e7ca85e Fix overdraw issues
Overdraw caused high CPU usage (~25-50% of CPU when open but no
touch events). Now the CPU is only used to process input events and
execute any follow up events. If FlorisBoard is nw left open without
using it, CPU usage goes down to 0%.
2021-01-05 03:14:46 +01:00
Patrick Goldinger
207845d46f Rework popup UI implementation 2021-01-04 18:18:08 +01:00
Patrick Goldinger
707d54b6f4 Merge pull request #118 from yashx/border
Added Option to Hide keys border
2021-01-01 13:03:33 +01:00
Patrick Goldinger
c01f167d49 Merge pull request #117 from yashx/switchKeyboard
Added gesture option to quick switch to previous keyboard
2020-12-31 14:44:07 +01:00
Patrick Goldinger
176ca00f66 Merge pull request #119 from florisboard/feat-improve-layout-definition
Improve layout definition structure
2020-12-31 14:35:19 +01:00
Patrick Goldinger
081cfdb0ee Add new libraries to README and include license texts 2020-12-31 13:31:50 +01:00
Patrick Goldinger
d9f94aecac Add documentation / Improve Asset interface 2020-12-31 13:31:05 +01:00
yashx
6691706aed Added Option to Hide keys border #87 2020-12-31 17:46:45 +05:30
yashx
1b77138798 Added gesture option to quick switch to previous keyboard 2020-12-31 00:36:43 +05:30
Patrick Goldinger
07ebd04052 Adapt new extended popup structure to json files 2020-12-30 19:49:03 +01:00
Patrick Goldinger
40c2bfd819 Adopt improved keyboard definition of existing layouts 2020-12-30 03:22:27 +01:00
Patrick Goldinger
855ad47674 Add default extended popup mapping 2020-12-30 03:21:35 +01:00
Patrick Goldinger
e032e4acb8 Complete rework of the key data structure + popups
This rework is a very important one, as it solves many conflicts which
arised while implementing special keyboard layouts like dvorak, etc.

With the new data structure, popup keys carry around way less baggage,
but at the same time the structure around the keys have improved a lot and provide more useful information for the popup manager.
This includes that smart hint/accent prioritization is now working
theoretically, now it just needs to be defined correctly in the
json files.

Also the layout and extended popup json files got a authors field
which indicates who the author is. At the current time not very
important, but this is a small step in modularization into assets,
which are the very base of extension support (but that's still a
looong way).

Also some parts of the LayoutManager got a code cleanup, which makes
especially the merging of popups and hints better readable.
2020-12-30 03:20:57 +01:00
Patrick Goldinger
fe6930fb76 Release v0.3.2 2020-12-27 21:48:48 +01:00
Patrick Goldinger
6a10f0a01a Swap underscore and percentage sign in symbols layout (#101) 2020-12-27 21:39:51 +01:00
Patrick Goldinger
30717eeb90 Merge pull request #115 from yashx/cleanUp
InputView Code Cleanup
2020-12-27 21:26:09 +01:00
Patrick Goldinger
a664ab18c9 Merge pull request #114 from florisboard/feat-toggle-ext-popup-priority
Add hint priority mode setting
2020-12-27 21:00:11 +01:00
yashx
f50983d7ab InputView Code Cleanup 2020-12-28 01:02:56 +05:30
Patrick Goldinger
be858802c5 Fix old hint strings not removed from translated files 2020-12-27 20:03:29 +01:00
Patrick Goldinger
1ba690e53a Add hint priority mode setting (#39) 2020-12-27 19:44:24 +01:00
Patrick Goldinger
e16f81d350 Merge pull request #111 from yashx/timber
Switch to Timber for Logging
2020-12-27 18:27:10 +01:00
Patrick Goldinger
0de2039d72 Merge pull request #110 from florisboard/feat-private-mode
Add private mode (aka incognito mode) base
2020-12-27 16:23:02 +01:00
Patrick Goldinger
50b6a63468 Add private mode theme attributes 2020-12-27 16:16:53 +01:00
yashx
8cb644b418 Switch to Timber for Logging 2020-12-27 11:33:09 +05:30
Patrick Goldinger
f138124670 Add private mode (aka incognito mode) base (#106) 2020-12-26 23:24:27 +01:00
Patrick Goldinger
0f76d7f9df Merge pull request #107 from yashx/undoRedo
Added Undo Redo Buttons to Quick Actions in Smart Bar
2020-12-26 21:20:35 +01:00
Patrick Goldinger
27b9ec4628 Merge pull request #108 from florisboard/fix-typing-ux
Improve input UX and performance
2020-12-26 21:13:51 +01:00
Patrick Goldinger
ac733ed1dc Further improve input UX 2020-12-26 21:09:11 +01:00
yashx
6d15708f95 Switch to emulating hardware key press to paste 2020-12-26 22:03:54 +05:30
Patrick Goldinger
4377f3e41c Improve input performance by avoiding object allocation 2020-12-26 16:26:58 +01:00
Patrick Goldinger
1e690018d7 Merge pull request #100 from yashx/hardwareDelete
Switch to emulating hardware key press to delete
2020-12-26 11:37:32 +01:00
yashx
93bb5d2714 Added Undo Redo Buttons to Quick Actions in Smart Bar 2020-12-26 15:23:38 +05:30
Patrick Goldinger
ad2b08a342 Merge pull request #102 from The-Quantum-Alpha/patch-1
Create canadian_french.json
2020-12-26 03:55:21 +01:00
The Quantum Alpha
9e6508cee4 yeah, whatever with the config.json
🇨🇦fr
2020-12-25 21:49:12 -05:00
The Quantum Alpha
f735c138fb Create canadian_french.json
qwerty, but with éàè
2020-12-25 20:56:49 -05:00
yashx
d663947fec Switch to emulating hardware key press to delete 2020-12-26 01:31:22 +05:30
Patrick Goldinger
c800617e26 Merge pull request #99 from yashx/arrowsFix
Fix left and right arrow in clipboard cursor row
2020-12-25 20:57:54 +01:00
yashx
f47c7abaf3 Fix left and right arrow in clipboard cursor row 2020-12-26 00:27:40 +05:30
Patrick Goldinger
faf06ee234 Merge pull request #97 from yashx/deleteGesture
Delete Key gesture Improvents
2020-12-25 19:39:33 +01:00
Patrick Goldinger
07c41f9c27 Merge pull request #98 from florisboard/fix-key-delete-crash-on-hold
Fix key delete crash on holding down
2020-12-25 19:34:10 +01:00
Patrick Goldinger
80a0d9edab Fix scheduled timer crash in media and editing as well 2020-12-25 19:20:12 +01:00
Patrick Goldinger
cd943a9d4a Fix key delete crash on holding down
Fix key delete crash on holding down 2
2020-12-25 19:12:06 +01:00
yashx
c3d3107b12 Added Delete Words Precisely 2020-12-25 20:34:36 +05:30
yashx
b91fac8e76 Fix Delete current word 2020-12-25 18:11:30 +05:30
Patrick Goldinger
e2c784f4cf Merge pull request #92 from Surendrajat/ci 2020-12-24 16:07:57 +01:00
Surendrajat
f83bdd8a28 Enable automatic build CI workflows
fix executable permission

add badge in README too

upload artifact

fix name
2020-12-24 15:55:23 +01:00
Patrick Goldinger
dc10a459ca Release v0.3.1 2020-12-23 00:42:22 +01:00
Patrick Goldinger
4bea68f151 Update translations from Crowdin 2020-12-23 00:25:21 +01:00
Patrick Goldinger
daa8ce71ac Remove unused legacy subtype attributes
isAsciiCapable and isEmojiCapable have no real use in FlorisBoard,
and as the Android InputMethodSubtype class will never be used,
there's no reason to keep these in. Removing them lets the config
look more clean.
2020-12-22 20:51:51 +01:00
Patrick Goldinger
f06f475e89 Merge pull request #90 from jeremiah-miller/esperanto_layout
Added Esperanto keyboard layout
2020-12-22 20:21:27 +01:00
Jeremiah Miller
b784d0805c Merge branch 'master' into esperanto_layout 2020-12-22 11:39:47 -07:00
bbgun7
c245c6a37c Added popups to en.json so that all english characters can be accessed from the esperanto layout 2020-12-22 11:38:33 -07:00
bbgun7
264a287171 Fixed popups for esperanto (eo) layout, and added eo layout variant 2020-12-22 11:37:29 -07:00
Patrick Goldinger
82d82466c6 Add Dvorak keyboard layout (#72) 2020-12-21 23:30:32 +01:00
Patrick Goldinger
0242d24cd1 Add Colemak keyboard layout (#72) 2020-12-21 22:05:32 +01:00
Patrick Goldinger
76e683bfec Fix event listener NullPointerException (#73, #81) 2020-12-21 20:02:28 +01:00
Patrick Goldinger
ee1988d98e Merge pull request #91 from florisboard/feat-smartbar-rework
Smartbar rework (Milestone v0.4.0 / Module A)
2020-12-21 18:55:28 +01:00
Patrick Goldinger
fe5f0d18ac Update README.md feature roadmap 2020-12-21 18:50:09 +01:00
Patrick Goldinger
41527e4f23 Reimplement clipboard suggestions 2020-12-21 18:02:10 +01:00
Patrick Goldinger
66fb1c5873 Improve Smartbar display logic
- Smartbar now doesn't show in number, phone and phone2 layouts.
- Remove "show instead" preference as it does not do anything anymore.
- Change one-handed icon to a smartphone, which should improve clarity.
2020-12-21 00:25:54 +01:00
Patrick Goldinger
05103214dd Add debug specific build.gradle settings
- This allows to have both a debug and release version of FlorisBoard
  on a single device.
2020-12-20 21:58:34 +01:00
Patrick Goldinger
bf9e2e4438 Add number row as character layout extension
- Number row is now not part of the Smartbar anymore, but is an
  extension of the character layout, meaning that it is possible to
  show both a number row and the Smartbar.
- The Smartbar can now be disabled in the preferences.
- Adjust height calculation when number row is shown.
- Fix Smartbar not applying calculated height correctly.
2020-12-20 19:58:23 +01:00
Patrick Goldinger
4209bdcfbe Fix syntax error in Hungarian extended popup list 2020-12-20 19:55:12 +01:00
bbgun7
31db482bb4 Added extended popups for esperanto layout 2020-12-19 21:06:44 -07:00
bbgun7
e33499dab5 Added Esperanto keyboard layout 2020-12-19 13:50:30 -07:00
Patrick Goldinger
92b99ff34e Rework Smartbar code base and layout XML
- The Smartbar XML layout has been completely changed and is now
  pretty solid.
- SmartbarManager's tasks have been split up: UI related things
  and the management of the state are now managed within the
  SmartbarView, setting the values and listening to events is now done within TextInputManager. Removing SmartbarManager was an important
  step because the code and logic was just a pure mess.
- SmartbarView is now responsible to manage the state, show and hide
  features based on various parameters from the keyboard core.
2020-12-17 23:09:09 +01:00
Patrick Goldinger
f991c6479b Add feature roadmap to README.md 2020-12-13 23:58:51 +01:00
Patrick Goldinger
5a45b1600a Merge pull request #75 from zoli111/master
Add Hungarian layout
2020-12-13 23:17:39 +01:00
zoli111
79f884b2a0 Fix Hungarian layout 2020-12-10 18:46:59 +01:00
zoli111
22330ad67b Add Hungarian layout 2020-12-08 22:32:34 +01:00
Patrick Goldinger
7f50a5aa77 Update CONTRIBUTING.md
Remove "!" preceding Crowdin link as it was treated as image.
2020-12-08 02:06:06 +01:00
Patrick Goldinger
de389918be Release v0.3.0 2020-12-06 23:48:59 +01:00
Patrick Goldinger
4a57829105 Update translations from Crowdin 2020-12-06 23:29:30 +01:00
Patrick Goldinger
bc6ca8c7fc Improve precise character delete swipe (#25)
- Lowered distance threshold for move swipes
- Fix delete swipe not recognized when only one character was selected
2020-12-05 20:41:36 +01:00
Patrick Goldinger
0ffe0c915e Fix symbol hint not accounting for missing shift (#68)
- The symbols are now correctly taken from the symbol layout, without
  the switch to symbol2 and delete key.
2020-12-04 18:56:38 +01:00
Patrick Goldinger
392699f333 Fix keyboard UI not displaying correctly for rtl languages (#69) 2020-12-04 18:38:23 +01:00
Patrick Goldinger
cf801c02fd Merge pull request #66 from HeiWiper/master
Added an Arabic keyboard and mod, and changed persian ID to 801
2020-12-04 18:04:33 +01:00
Patrick Goldinger
665356f77b Major improvements in auto sizing(#48, #50, #61)
- Keyboard height can - besides of the preset values - be set between
  50% and 150%
- Key font size range has been extended to 50%-150%
- Key font size multiplier now affects the popup as well
- Key popup size scales with the keyboard height value
- Fix key size algorithm not working on xxhdpi screens
- Improve key popup manager backend
2020-12-03 23:43:18 +01:00
Hei Wiper
48c356a569 Added an Arabic keyboard and mod, and changed persian ID to 801 2020-12-03 23:11:52 +01:00
Patrick Goldinger
60eb92e92a Fix bottom offset not applying correctly (#58) 2020-12-02 19:57:19 +01:00
Patrick Goldinger
602ffc2a93 Add option to adjust font size multiplier (#48)
- Also improve default key font size calculation parameters.
2020-12-02 18:27:59 +01:00
Patrick Goldinger
dbacc0e466 Fix release badge in README.md not pointing to releases 2020-12-01 20:53:05 +01:00
Patrick Goldinger
1307f401cc Release v0.2.6 2020-12-01 20:46:24 +01:00
Patrick Goldinger
ca6006767b Improve key font sizing (#48)
- Key font size is now generated with a better algorithm.
- Key font size in general is now bigger and the letter/white space
  ratio has been improved.
2020-12-01 19:57:58 +01:00
Patrick Goldinger
2202db53ba Add reference to permission list to README.md 2020-12-01 16:46:08 +01:00
Patrick Goldinger
321f19272e Fix Smartbar number row disappearing incorrectly (#52) 2020-11-30 22:24:06 +01:00
Patrick Goldinger
06a8a04020 Improve keyboard height calculation (#50) 2020-11-30 22:03:33 +01:00
Patrick Goldinger
2a1f7c3217 Add Horizontal Ellipsis (Three-dots) character to symbols (#51) 2020-11-30 18:18:02 +01:00
Patrick Goldinger
76952d55fe Release v0.2.5 2020-11-29 23:30:19 +01:00
florisboard-bot
1f560f8b6b Update translations from Crowdin (#49) 2020-11-29 23:11:26 +01:00
Patrick Goldinger
33bdc52354 Add precise delete key gesture for characters (#25) 2020-11-29 22:46:10 +01:00
Patrick Goldinger
97b795aed0 Fix status bar incorrectly drawn in Android 11 (#43) 2020-11-29 18:33:52 +01:00
Patrick Goldinger
bb44362701 Fix EmojiKeyboardView init crash in Android 6.0 (#41) 2020-11-28 19:11:18 +01:00
Patrick Goldinger
bab20c5baa Add comments to strings.xml to help translators
- This is done to help translators in Crowdin better understanding
  in which context a string is used.
2020-11-27 19:45:11 +01:00
Patrick Goldinger
a3000fe111 Update README.md and CONTRIBUTING.md
- Now includes links to the Crowdin project.
- Add Crowdin badge.
- Update some paragraphs and the layout.
2020-11-26 19:56:15 +01:00
florisboard-bot
d4d2f52683 Update Crowdin configuration file 2020-11-26 00:28:39 +01:00
florisboard-bot
10ef340559 Update Crowdin configuration file 2020-11-26 00:09:38 +01:00
Patrick Goldinger
5b77262186 Prepare string resource files for Crowdin 2020-11-25 21:47:53 +01:00
Patrick Goldinger
8ce56b1bf9 Fix error log output omitting line separator characters 2020-11-24 19:26:20 +01:00
Patrick Goldinger
94667e8363 Fix keyboard crashing when long pressing delete key (#40) 2020-11-24 18:33:27 +01:00
Patrick Goldinger
970b5eb82a Release v0.2.4 2020-11-22 21:46:36 +01:00
Patrick Goldinger
a2ceed4521 Improve Smartbar layout / Add clipboard content suggestions (#38)
- This commit adds clipboard content suggestions. These suggestions do
  only show if suggestions in general are turned on.
- The suggestions show for both text and images in the clipboard, but
  do currently only work for text.
- Clipboard/Cursor row is now a proper KeyboardView, which gets rid of
  the hardcoded keys for the arrows / clipboard commands.
- Fix errors in doc strings.
- Fix other logic errors in TextInputManager and EditorInstance.
2020-11-22 21:25:35 +01:00
Patrick Goldinger
6d7825e129 Add crash handler and error detail form
- This crash handler catches nearly all uncaught errors and notifies
  the user about it. If an uncaught error occurs in the FlorisBoard
  service initialization, the handler detects this and switches to
  another installed keyboard.
- The error detail form contains the captured stacktrace and adds
  a copy to clipboard functionality as well as a button to the
  GitHub issue tracker.
2020-11-19 23:59:23 +01:00
Patrick Goldinger
10c1a82995 Rework core to fix potential crashes when entering text 2020-11-17 18:34:57 +01:00
Patrick Goldinger
267a39e870 Add basic clipboard text suggestion to Smartbar (#38) 2020-11-16 23:52:51 +01:00
Patrick Goldinger
f6fcbbcc34 Update project dependencies and build.gradle 2020-11-16 18:32:55 +01:00
Patrick Goldinger
f98b3cec4b Improve layout and behavior of number row in Smartbar (#31)
- Number row is now a proper keyboard instead of a LinearLayout with
  hardcoded keys.
- Number row takes whole Smartbar width by hiding the Smartbar arrow
  (improves size per number key, which allows it to be more easily
  touchable).
2020-11-15 23:43:16 +01:00
Patrick Goldinger
e5a942be9f Add support for raw text editors (e.g. terminals, ...)
- FlorisBoard is now able to perform input on raw input editors (editors
  which either have an incomplete, faulty or purposely simple
  implementation).
- Especially targeted at terminal apps, as these apps do not manage the
  state of the input but only forward it.
2020-11-13 20:56:41 +01:00
Patrick Goldinger
edc63aa680 Release v0.2.3 2020-11-11 23:08:55 +01:00
Patrick Goldinger
23def145b2 Finish reworking core (#35 #33) 2020-11-11 22:59:27 +01:00
Patrick Goldinger
3f7bd4f65d Fix delete key not working for emojis / Fix several other bugs 2020-11-10 23:44:07 +01:00
Patrick Goldinger
7b91d4f9d3 Add EditorInstance object to better manage state of input
- EditorInstance is an improved EditorInfo object which also holds the
  current state of the input like text, selection, ...
- Should help in cleaning up TextInputManager and resolve issues around
  non-updating caps states, etc.
2020-11-08 22:34:05 +01:00
Patrick Goldinger
175369f7d7 Improve onStartInputView behaviour 2020-11-05 19:41:09 +01:00
Patrick Goldinger
79c5acc007 Improve debugging inspection output
- Needed for inspection why FlorisBoard behaves strangely in some apps
2020-11-04 21:24:06 +01:00
Patrick Goldinger
94d470dd96 Fix font sizing bug in KeyView
- Calculation may require 2 iterations until the correct size is found
  because both width and height can be <=0 or >=0
2020-11-03 18:56:11 +01:00
Patrick Goldinger
ee9d61ad1e Add auto font sizing for text input keys (#32)
- Font of keys is now adjusted accordingly to the keyboard height
  preference.
- Affects hinted symbols / numbers too.
2020-11-01 22:22:14 +01:00
Patrick Goldinger
a3c7b538d0 Add option to remember caps lock state (#30)
- Located in Settings > Typing > Remember caps lock state
- Defaults to false (do not remember state)
2020-10-30 16:49:47 +01:00
279 changed files with 16717 additions and 4830 deletions

View File

@@ -27,8 +27,8 @@ assignees: ''
3. Scroll down to '....'
4. See error
<!--
<!-- (remove this line if you paste a log)
```
If applicable, paste the captured debug log here.
```
-->
(remove this line if you paste a log) -->

32
.github/workflows/android.yml vendored Normal file
View File

@@ -0,0 +1,32 @@
name: FlorisBoard CI
on:
push:
branches: [master]
pull_request:
branches: [master]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- uses: actions/cache@v2
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Build with Gradle
run: ./gradlew clean assemble
- uses: actions/upload-artifact@v2
with:
name: app-debug.apk
path: app/build/outputs/apk/debug/app-debug.apk

View File

@@ -2,8 +2,8 @@
First off, thanks for considering contributing to FlorisBoard!
There are several ways to contribute to FlorisBoard. This document provides some
general guidelines for each type of contribution.
There are several ways to contribute to FlorisBoard. This document
provides some general guidelines for each type of contribution.
## Giving general feedback
@@ -11,60 +11,50 @@ Either use the review function within Google Play or email me at
[florisboard@patrickgold.dev](mailto:florisboard@patrickgold.dev). I
love to hear from you!
## Translations
To make FlorisBoard accessible in as many languages as possible, the
platform [Crowdin](https://crowdin.florisboard.patrickgold.dev) is used
to crowdsource and manage translations. This is the only source of
translations from now on - **PRs that add/update translations are no
longer accepted.** The list of languages in Crowdin covers the top 20
languages, but feel free to email me at
[florisboard@patrickgold.dev](mailto:florisboard@patrickgold.dev) to
request a language and I'll add it.
## Adding a new feature or making large changes
If you intend to add a new feature or to make large changes, please discuss this
first through a proposal on GitHub. Discussing your idea enables both you and the
dev team that we are on the same page before you start on working on your change.
If you have any questions, feel free to ask for help at any time!
If you intend to add a new feature or to make large changes, please
discuss this first through a proposal on GitHub. Discussing your idea
enables both you and the dev team that we are on the same page before
you start on working on your change. If you have any questions, feel
free to ask for help at any time!
## Adding a new keyboard layout / dictionary for locale
As FlorisBoard is currently in alpha stage, things might change drastically. This
also includes the config scheme of keyboard layouts. To prevent incompatible
configs because some features and structures may change, please do not add this
kind of content yet. As FlorisBoard's state progresses and its core stabilizes,
you will be able to add keyboard layouts.
You can now oficially add layouts to FlorisBoard as described below.
FlorisBoard's core has stabilized enough that adding new content is
safe, although there will be some changes in the future.
## Translating FlorisBoard
Currently you need to modify `app/src/main/assets/ime/config.json` to
add the filename of the language/layout to the `characterLayouts`
section and the `defaultSubtypes` section, making sure to include
the language's IETF BCP 47 code ([ISO 639-1 language code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes)
and [ISO 3166-1 region code](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2#Officially_assigned_code_elements)).
For example, Dutch as spoken in Belgium is `nl-be`. Use a unique value
for `id` to avoid possible crahses caused by duplicate ids.
Before starting to translate, when adding a new translation please file
an issue stating that you want to translate FlorisBoard into a language.
Once this gets approved you can start translating. When updating an
already existing translation file you can just send a PR directly.
Add the keyboard layout at `app/src/main/assets/ime/text/characters/<preferredLayout_name_here>.json`,
with `code` referring to the characters codepoint and `label` being the
respective unicode character.
If you are not familiar with PRs, check out this guide:
[https://www.gun.io/blog/how-to-github-fork-branch-and-pull-request](https://www.gun.io/blog/how-to-github-fork-branch-and-pull-request)
Notes for tips below:
- Replace `<language>` with the language you want to add
- Replace `<code>` with the ISO 639-1 code of the language you want to
add
([List of codes](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes))
### Tips when adding a new translation
- To add the new translation file, navigate to `app/src/main/res/values`
and copy the file `strings.xml` into the folder
`app/src/main/res/values-<code>` (you have to create this folder)
- Translate only the phrases inside the brackets, leave the name
attribute as it is
E.g.: `<string name="hello_string">Hello World!</string>`
`<string name="hello_string">Ciao mondo!</string>`
- When finished translating, commit your changes locally, as the commit
message use `Add <language> translation`
- Push your change(s) and create the PR. When everything checks out, it
will get accepted.
### Tips when updating a translation
- To update a translation, check the `strings.xml` in
`app/src/main/res/values` for newly added strings and add them to the
translation file in `app/src/main/res/values-<code>`
- When finished translating, commit your changes locally, as the commit
message use `Update <language> translation`
- Push your change(s) and create the PR. When everything checks out, it
will get accepted.
Any accents or diacritics that should be exposed via long press can be
added at `assets/ime/text/characters/extended_popups/<languageTag_name_here>.json`.
For each key, you can add 1 main and several relevant accents. The main
accent should be used for accents which are important for the language
you add. The main field is used for determining if a hint or an accent
should take priority, so please make sure to leave main empty and just
use relevant for accents which are not-so important.
## Bug reporting
@@ -75,6 +65,11 @@ use the premade [issue template](.github/ISSUE_TEMPLATE/bug_report.md)
for bug reporting. This makes it easy for us to understand what the bug
is and how to solve it.
### Capturing ADB debug logs
### Capturing error logs
[[ TODO: create tutorial ]]
Logs are captured by FlorisBoard's crash handler, which gives you the
ability to copy it to the clipboard and paste it in GitHub. This is the
preferred way to capture logs.
Alternatively, you can also use ADB (Android Debug Bridge) to capture
the error log. This is recommended for experienced users only.

141
README.md
View File

@@ -1,13 +1,18 @@
# FlorisBoard
<img align="left" width="80" height="80"
src="fastlane/metadata/android/en-US/images/icon.png" alt="App icon">
An open-source keyboard for Android. Currently in alpha stage.
# FlorisBoard [![Release](https://img.shields.io/github/v/release/florisboard/florisboard)](https://github.com/florisboard/florisboard/releases) [![Crowdin](https://badges.crowdin.net/florisboard/localized.svg)](https://crowdin.florisboard.patrickgold.dev) ![FlorisBoard CI](https://github.com/florisboard/florisboard/workflows/FlorisBoard%20CI/badge.svg?event=push)
#### Public Alpha Test Programme
**FlorisBoard** is a free and open-source keyboard for Android 6.0+
devices. It aims at being modern, user-friendly and customizable while
fully respecting your privacy. Currently in alpha/early-beta state.
## Public Alpha Test Programme
Wanna try it out on your device? Use one of the following options:
_A. IzzySoft's repo for F-Droid_:
_A. Get it on F-Droid_:
[<img src="https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png" height="64" alt="IzzySoft repo badge">](https://apt.izzysoft.de/fdroid/index/apk/dev.patrickgold.florisboard)
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" height="64" alt="F-Droid badge">](https://f-droid.org/packages/dev.patrickgold.florisboard)
_B. Google Play Public Alpha Test_:
@@ -26,25 +31,25 @@ tester, follow these steps:
4. Finished! You will receive future versions of FlorisBoard via Google
Play.
With the v0.4.0 release FlorisBoard will enter the public beta in GPlay, allowing to directly search
for and download FlorisBoard without prior joining the alpha group.
_C. Use the APK provided in the release section of this repo_
##### Giving feedback
### Giving feedback
If you want to give feedback to FlorisBoard, there are several ways to
do so, as listed in the [contribution guidelines](CONTRIBUTING.md).
Thank you for contributing to FlorisBoard!
##### Note on F-Droid release
FlorisBoard is currently available through Google Play and IzzySoft's
repo for F-Droid, but is currently in the inclusion process for the main
F-Droid repo. Planned proper F-Droid release is version 0.3.0.
do so, as listed [here](CONTRIBUTING.md#giving-general-feedback).
---
<img src="https://patrickgold.dev/media/previews/florisboard.png"
height="256" alt="Preview Image">
<img align="right" height="256"
src="https://patrickgold.dev/media/previews/florisboard-preview-day.png"
alt="Preview image">
## Feature roadmap
## Implemented features
This list contains all implemented and fully functional features
FlorisBoard currently has to offer. For planned features and its
milestones, please refer to the [Feature roadmap](#feature-roadmap).
### Basics
* [x] Implementation of the keyboard core (InputMethodService)
@@ -55,47 +60,36 @@ height="256" alt="Preview Image">
* [x] Key press sound/vibration
* [x] Portrait orientation support
* [x] Landscape orientation support (needs tweaks)
* [ ] Tablet screen support (0.4.0)
### Layouts
* [x] Latin character layouts (QWERTY, QWERTZ, AZERTY, Swiss, Spanish,
Norwegian, Swedish/Finnish, Icelandic, Danish); more coming in
future versions
* [x] Non-latin character layouts (Persian)
Norwegian, Swedish/Finnish, Icelandic, Danish, Hungarian,
Croatian, Polish, Romanian); more coming in future versions
* [x] Non-latin character layouts (Arabic, Persian, Greek, Russian
(JCUKEN))
* [x] Adapt to situation in app (password, url, text, etc. )
* [x] Special character layout(s)
* [x] Numeric layout
* [x] Numeric layout (advanced)
* [x] Phone number layout
* [x] Emoji layout (tweaks: 0.3.0)
* [x] Emoji layout
* [x] Emoticon layout
* [ ] Kaomoji layout (0.3.0)
### Preferences
* [x] Setup wizard
* [x] Preferences screen
* [x] Customize look and behaviour of keyboard
* [x] Theme presets (currently only day/night theme)
* [x] Theme presets (currently only day/night theme + borderless)
* [x] Theme customization
* [ ] Theme import/export (0.4.0 or 0.5.0)
* [x] Subtype selection (language/layout)
* [x] Keyboard behaviour preferences
* [ ] Text suggestion / Auto correct preferences (0.4.0 or 0.5.0)
* [x] Gesture preferences (0.3.0)
### Composing suggestions (0.4.0 or 0.5.0)
* [ ] Auto suggest words from precompiled dictionary
* [ ] Auto suggest words from user dictionary
* [ ] Auto suggest contacts
* [ ] Multilingual typing
* [x] Gesture preferences
### Other useful features
* [x] One-handed mode
* [x] Clipboard/cursor tools
* [x] Integrated number row / symbols in character layouts (0.3.0)
* [ ] Floating keyboard (0.4.0)
* [x] Gesture support (0.3.0)
* [ ] Glide typing (0.4.0)
* [x] Integrated number row / symbols in character layouts
* [x] Gesture support
* [x] Full integration in IME service list of Android (xml/method)
(integration is internal-only, because Android's default subtype
implementation not really allows for dynamic language/layout
@@ -104,11 +98,72 @@ height="256" alt="Preview Image">
* [ ] (dev only) Generate well-structured documentation of code
* [ ] ...
Note:
## Feature roadmap
This section describes the features which are planned to be implemented
in FlorisBoard for the next major versions, modularized into sections.
Please note that the milestone due dates are only raw estimates and will
most likely be delayed back, even though I'm eager to stick to these as
close as possible.
(?) = not sure if it will be implemented
### [v0.4.0](https://github.com/florisboard/florisboard/milestone/4)
- Module A: Smartbar rework (Implemented with #91)
- Ability to enable/disable Smartbar (features below thus only work if
Smartbar is enabled)
- Dynamic switching between clipboard tools and word suggestions
- Ability to show both the number row and word suggestions at once
- Better icons in quick actions
- Complete rework of the Smartbar code base and the Smartbar layout
definition in XML
(0.x.0) = planned version when feature will be implemented.
- Module B: Composing suggestions
- Auto-suggestion of words based of precompiled dictionaries
- Management of custom dictionary entries
- Opt-in only: Learning of often typed word pais to better predict next
words over time. Data collected here is stored locally and never leaves
the user's device.
- Module C: Extension packs (base implementation with #162)
- Ability to load dictionaries (and later potentially other cool
features too) only if needed to keep the core APK size small
- Currently unclear how exactly this will work, but this is definitely
a must-have feature
- Module D: Glide typing
- Swiping over the characters will automatically convert this to a word
- Possibly also add improvements based on the Flow keyboard
- Module E: Theme rework (Implemented with #162)
- Themes are now based on the Asset schema
- Dynamic theme creation
- Different theme modes (`Always day`, `Always dark`, `Follow system`
and `Follow time`)
- Define a separate theme both for day and night theme
- Adapt to app theme if possible
### [v0.5.0](https://github.com/florisboard/florisboard/milestone/5)
There's no exact roadmap yet but it is planned that the media part of
FlorisBoard (emojis, emoticons, kaomoji) gets a rework. Also as an extension
(requires v0.4.0/Module C) GIF support is planned.
### > v0.5.0
This is completely open as of now and will gather planned features as time
passes...
Backlog (currently not assigned to any milestone):
- Theme import/export
- Floating keyboard
## Contributing
Wanna contribute to FlorisBoard? That's great to hear! There are lots of
different ways to help out. Bug reporting, making pull requests,
translating FlorisBoard to make it more accessible, etc. For more
information see the ![contributing guidelines](CONTRIBUTING.md). Thank
you for your help!
## List of permissions FlorisBoard requests
Please refer to this [page](https://github.com/florisboard/florisboard/wiki/List-of-permissions-FlorisBoard-requests)
to get more information on this topic.
## Used libraries, components and icons
* [Google Flexbox Layout for Android](https://github.com/google/flexbox-layout)
@@ -119,6 +174,12 @@ Note:
[square](https://github.com/square)
* [ColorPicker preference](https://github.com/jaredrummler/ColorPicker) by
[Jared Rummler](https://github.com/jaredrummler)
* [Timber](https://github.com/JakeWharton/timber) by
[JakeWharton](https://github.com/JakeWharton)
* [kotlin-result](https://github.com/michaelbull/kotlin-result) by
[Michael Bull](https://github.com/michaelbull)
* [expandable-fab](https://github.com/nambicompany/expandable-fab) by
[Nambi](https://github.com/nambicompany)
## License
```

View File

@@ -6,12 +6,21 @@ android {
compileSdkVersion 29
buildToolsVersion "29.0.2"
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
defaultConfig {
applicationId "dev.patrickgold.florisboard"
minSdkVersion 23
targetSdkVersion 29
versionCode 14
versionName "0.2.2"
versionCode 23
versionName "0.3.4"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@@ -21,9 +30,20 @@ android {
}
buildTypes {
debug {
applicationIdSuffix ".debug"
resValue "string", "app_name", "FlorisBoard Debug"
}
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
resValue "string", "app_name", "FlorisBoard"
}
}
testOptions {
unitTests {
includeAndroidResources = true
}
}
}
@@ -33,19 +53,24 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.core:core-ktx:1.3.1'
implementation 'androidx.preference:preference:1.1.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.0'
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.preference:preference-ktx:1.1.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
testImplementation 'junit:junit:4.12'
testImplementation 'androidx.test:core:1.2.0'
testImplementation 'androidx.test:core:1.3.0'
testImplementation 'org.mockito:mockito-core:1.10.19'
testImplementation 'org.mockito:mockito-inline:2.13.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
testImplementation 'org.robolectric:robolectric:4.4'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
implementation 'com.google.android:flexbox:2.0.1'
implementation "com.squareup.moshi:moshi-kotlin:1.9.2"
implementation 'com.google.android.material:material:1.2.0'
implementation 'com.squareup.moshi:moshi-adapters:1.9.2'
implementation 'com.google.android.material:material:1.2.1'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.7"
implementation 'com.jaredrummler:colorpicker:1.1.0'
implementation 'com.jakewharton.timber:timber:4.7.1'
implementation "com.michael-bull.kotlin-result:kotlin-result:1.1.9"
implementation 'com.nambimobile.widgets:expandable-fab:1.0.2'
}

View File

@@ -21,6 +21,7 @@
<uses-permission android:name="android.permission.VIBRATE"/>
<application
android:name=".ime.core.FlorisApplication"
android:allowBackup="false"
android:extractNativeLibs="false"
android:icon="@mipmap/ic_launcher"
@@ -66,6 +67,20 @@
</intent-filter>
</activity-alias>
<!-- Theme Selector Activity -->
<activity
android:name="dev.patrickgold.florisboard.settings.ThemeManagerActivity"
android:icon="@mipmap/ic_launcher"
android:label="@string/settings__title"
android:theme="@style/SettingsTheme"/>
<!-- Theme Editor Activity -->
<activity
android:name="dev.patrickgold.florisboard.settings.ThemeEditorActivity"
android:icon="@mipmap/ic_launcher"
android:label="@string/settings__theme_editor__title"
android:theme="@style/SettingsTheme"/>
<!-- About Activity -->
<activity
android:name="dev.patrickgold.florisboard.settings.AboutActivity"
@@ -90,6 +105,13 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:theme="@style/SettingsTheme"/>
<!-- Crash Dialog Activity -->
<activity
android:name="dev.patrickgold.florisboard.crashutility.CrashDialogActivity"
android:icon="@mipmap/ic_launcher"
android:label="@string/crash_dialog__title"
android:theme="@style/CrashDialogTheme"/>
</application>
</manifest>

View File

@@ -4,6 +4,7 @@
"qwerty": "QWERTY",
"qwertz": "QWERTZ",
"azerty": "AZERTY",
"bepo": "BÉPO",
"spanish": "Spanish (QWERTY)",
"norwegian": "Norwegian (QWERTY)",
"swedish_finnish": "Swedish/Finnish (QWERTY)",
@@ -12,176 +13,177 @@
"swiss_german": "Swiss German (QWERTZ)",
"swiss_french": "Swiss French (QWERTZ)",
"swiss_italian": "Swiss Italian (QWERTZ)",
"persian": "Persian"
"hungarian": "Hungarian (QWERTZ)",
"persian": "Persian",
"arabic": "Arabic",
"esperanto": "Esperanto",
"esperanto_with_hx": "Esperanto with 'ĥ'",
"colemak": "Colemak",
"dvorak": "Dvorak",
"jcuken_russian": "Russian (JCUKEN)",
"canadian_french": "Canadian French (QWERTY)",
"greek": "Ελληνικά"
},
"defaultSubtypes": [
{
"id": 101,
"languageTag": "en-US",
"preferredLayout": "qwerty",
"isAsciiCapable": true,
"isEmojiCapable": true
"preferredLayout": "qwerty"
},
{
"id": 102,
"languageTag": "en-UK",
"preferredLayout": "qwerty",
"isAsciiCapable": true,
"isEmojiCapable": true
"preferredLayout": "qwerty"
},
{
"id": 103,
"languageTag": "en-CA",
"preferredLayout": "qwerty",
"isAsciiCapable": true,
"isEmojiCapable": true
"preferredLayout": "qwerty"
},
{
"id": 104,
"languageTag": "en-AU",
"preferredLayout": "qwerty",
"isAsciiCapable": true,
"isEmojiCapable": true
"preferredLayout": "qwerty"
},
{
"id": 201,
"languageTag": "de-DE",
"preferredLayout": "qwertz",
"isAsciiCapable": true,
"isEmojiCapable": true
"preferredLayout": "qwertz"
},
{
"id": 202,
"languageTag": "de-AT",
"preferredLayout": "qwertz",
"isAsciiCapable": true,
"isEmojiCapable": true
"preferredLayout": "qwertz"
},
{
"id": 203,
"languageTag": "de-CH",
"preferredLayout": "swiss_german",
"isAsciiCapable": true,
"isEmojiCapable": true
"preferredLayout": "swiss_german"
},
{
"id": 301,
"languageTag": "fr-FR",
"preferredLayout": "azerty",
"isAsciiCapable": true,
"isEmojiCapable": true
"preferredLayout": "azerty"
},
{
"id": 302,
"languageTag": "fr-CA",
"preferredLayout": "qwerty",
"isAsciiCapable": true,
"isEmojiCapable": true
"preferredLayout": "canadian_french"
},
{
"id": 303,
"languageTag": "fr-CH",
"preferredLayout": "swiss_french",
"isAsciiCapable": true,
"isEmojiCapable": true
"preferredLayout": "swiss_french"
},
{
"id": 401,
"languageTag": "it-IT",
"preferredLayout": "qwerty",
"isAsciiCapable": true,
"isEmojiCapable": true
"preferredLayout": "qwerty"
},
{
"id": 402,
"languageTag": "it-CH",
"preferredLayout": "swiss_italian",
"isAsciiCapable": true,
"isEmojiCapable": true
"preferredLayout": "swiss_italian"
},
{
"id": 501,
"languageTag": "es-ES",
"preferredLayout": "spanish",
"isAsciiCapable": true,
"isEmojiCapable": true
"preferredLayout": "spanish"
},
{
"id": 502,
"languageTag": "es-US",
"preferredLayout": "spanish",
"isAsciiCapable": true,
"isEmojiCapable": true
"preferredLayout": "spanish"
},
{
"id": 503,
"languageTag": "es-419",
"preferredLayout": "spanish",
"isAsciiCapable": true,
"isEmojiCapable": true
"preferredLayout": "spanish"
},
{
"id": 601,
"languageTag": "pt-PT",
"preferredLayout": "qwerty",
"isAsciiCapable": true,
"isEmojiCapable": true
"preferredLayout": "qwerty"
},
{
"id": 602,
"languageTag": "pt-BR",
"preferredLayout": "qwerty",
"isAsciiCapable": true,
"isEmojiCapable": true
"preferredLayout": "qwerty"
},
{
"id": 701,
"languageTag": "nb-NO",
"preferredLayout": "norwegian",
"isAsciiCapable": true,
"isEmojiCapable": true
"preferredLayout": "norwegian"
},
{
"id": 702,
"languageTag": "nn-NO",
"preferredLayout": "norwegian",
"isAsciiCapable": true,
"isEmojiCapable": true
"preferredLayout": "norwegian"
},
{
"id": 711,
"languageTag": "sv-SE",
"preferredLayout": "swedish_finnish",
"isAsciiCapable": true,
"isEmojiCapable": true
"preferredLayout": "swedish_finnish"
},
{
"id": 721,
"languageTag": "fi-FI",
"preferredLayout": "swedish_finnish",
"isAsciiCapable": true,
"isEmojiCapable": true
"preferredLayout": "swedish_finnish"
},
{
"id": 731,
"languageTag": "da-DK",
"preferredLayout": "danish",
"isAsciiCapable": true,
"isEmojiCapable": true
"preferredLayout": "danish"
},
{
"id": 741,
"languageTag": "is-IS",
"preferredLayout": "icelandic",
"isAsciiCapable": true,
"isEmojiCapable": true
"preferredLayout": "icelandic"
},
{
"id": 800,
"id": 801,
"languageTag": "fa-FA",
"preferredLayout": "persian",
"isAsciiCapable": true,
"isEmojiCapable": true
"preferredLayout": "persian"
},
{
"id": 901,
"languageTag": "ar",
"preferredLayout": "arabic"
},
{
"id": 1001,
"languageTag": "hu",
"preferredLayout": "hungarian"
},
{
"id": 1101,
"languageTag": "eo",
"preferredLayout": "esperanto"
},
{
"id": 1201,
"languageTag": "hr",
"preferredLayout": "qwertz"
},
{
"id": 1301,
"languageTag": "ru",
"preferredLayout": "jcuken_russian"
},
{
"id": 1401,
"languageTag": "el",
"preferredLayout": "greek"
},
{
"id": 1501,
"languageTag": "ro",
"preferredLayout": "qwerty"
},
{
"id": 1601,
"languageTag": "pl",
"preferredLayout": "qwerty"
}
]
}

View File

@@ -0,0 +1,47 @@
{
"type": "characters",
"name": "arabic",
"authors": [ "HeiWiper" ],
"direction": "rtl",
"modifier": "arabic",
"arrangement": [
[
{ "code": 1590, "label": "ض" },
{ "code": 1589, "label": "ص" },
{ "code": 1579, "label": "ث" },
{ "code": 1602, "label": "ق" },
{ "code": 1601, "label": "ف" },
{ "code": 1594, "label": "غ" },
{ "code": 1593, "label": "ع" },
{ "code": 1607, "label": "ه" },
{ "code": 1582, "label": "خ" },
{ "code": 1581, "label": "ح" },
{ "code": 1580, "label": "ج" }
],
[
{ "code": 1588, "label": "ش" },
{ "code": 1587, "label": "س" },
{ "code": 1610, "label": "ي" },
{ "code": 1576, "label": "ب" },
{ "code": 1604, "label": "ل" },
{ "code": 1575, "label": "ا" },
{ "code": 1578, "label": "ت" },
{ "code": 1606, "label": "ن" },
{ "code": 1605, "label": "م" },
{ "code": 1603, "label": "ك" },
{ "code": 1591, "label": "ط" }
],
[
{ "code": 1584, "label": "ذ" },
{ "code": 1569, "label": "ء" },
{ "code": 65157, "label": "ﺅ" },
{ "code": 1585, "label": "ر" },
{ "code": 1609, "label": "ى" },
{ "code": 1577, "label": "ة" },
{ "code": 1608, "label": "و" },
{ "code": 1586, "label": "ز" },
{ "code": 1592, "label": "ظ" },
{ "code": 1583, "label": "د" }
]
]
}

View File

@@ -1,6 +1,7 @@
{
"type": "characters",
"name": "azerty",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
@@ -14,7 +15,8 @@
{ "code": 105, "label": "i" },
{ "code": 111, "label": "o" },
{ "code": 112, "label": "p" }
], [
],
[
{ "code": 113, "label": "q" },
{ "code": 115, "label": "s" },
{ "code": 100, "label": "d" },
@@ -25,20 +27,23 @@
{ "code": 107, "label": "k" },
{ "code": 108, "label": "l" },
{ "code": 109, "label": "m" }
], [
],
[
{ "code": 119, "label": "w" },
{ "code": 120, "label": "x" },
{ "code": 99, "label": "c" },
{ "code": 118, "label": "v" },
{ "code": 98, "label": "b" },
{ "code": 110, "label": "n" },
{ "code": 39, "label": "'", "popup": [
{ "code": 8218, "label": "" },
{ "code": 8216, "label": "" },
{ "code": 8217, "label": "" },
{ "code": 8249, "label": "" },
{ "code": 8250, "label": "" }
] }
{ "code": 39, "label": "'", "popup": {
"relevant": [
{ "code": 8218, "label": "" },
{ "code": 8216, "label": "" },
{ "code": 8217, "label": "" },
{ "code": 8249, "label": "" },
{ "code": 8250, "label": "" }
]
} }
]
]
}

View File

@@ -0,0 +1,51 @@
{
"type": "characters",
"name": "bepo",
"authors": [ "salamandar" ],
"direction": "ltr",
"arrangement": [
[
{ "code": 98, "label": "b" },
{ "code": 233, "label": "é" },
{ "code": 112, "label": "p" },
{ "code": 111, "label": "o" },
{ "code": 232, "label": "è" },
{ "code": 118, "label": "v" },
{ "code": 100, "label": "d" },
{ "code": 108, "label": "l" },
{ "code": 106, "label": "j" },
{ "code": 122, "label": "z" },
{ "code": 119, "label": "w" }
],
[
{ "code": 97, "label": "a" },
{ "code": 117, "label": "u" },
{ "code": 105, "label": "i" },
{ "code": 101, "label": "e" },
{ "code": 99, "label": "c" },
{ "code": 116, "label": "t" },
{ "code": 115, "label": "s" },
{ "code": 114, "label": "r" },
{ "code": 110, "label": "n" },
{ "code": 109, "label": "m" },
{ "code": 231, "label": "ç" }
],
[
{ "code": 121, "label": "y" },
{ "code": 120, "label": "x" },
{ "code": 107, "label": "k" },
{ "code": 113, "label": "q", "popup": {
"relevant": [
{ "code": 8218, "label": "" },
{ "code": 8216, "label": "" },
{ "code": 8217, "label": "" },
{ "code": 8249, "label": "" },
{ "code": 8250, "label": "" }
]
} },
{ "code": 103, "label": "g" },
{ "code": 104, "label": "h" },
{ "code": 102, "label": "f" }
]
]
}

View File

@@ -0,0 +1,43 @@
{
"type": "characters",
"name": "canadian_french",
"authors": [ "The-Quantum-Alpha" ],
"direction": "ltr",
"arrangement": [
[
{ "code": 113, "label": "q" },
{ "code": 119, "label": "w" },
{ "code": 101, "label": "e" },
{ "code": 114, "label": "r" },
{ "code": 116, "label": "t" },
{ "code": 121, "label": "y" },
{ "code": 117, "label": "u" },
{ "code": 105, "label": "i" },
{ "code": 111, "label": "o" },
{ "code": 112, "label": "p" },
{ "code": 232, "label": "è" }
],
[
{ "code": 97, "label": "a" },
{ "code": 115, "label": "s" },
{ "code": 100, "label": "d" },
{ "code": 102, "label": "f" },
{ "code": 103, "label": "g" },
{ "code": 104, "label": "h" },
{ "code": 106, "label": "j" },
{ "code": 107, "label": "k" },
{ "code": 108, "label": "l" },
{ "code": 233, "label": "é" },
{ "code": 224, "label": "à" }
],
[
{ "code": 122, "label": "z" },
{ "code": 120, "label": "x" },
{ "code": 99, "label": "c" },
{ "code": 118, "label": "v" },
{ "code": 98, "label": "b" },
{ "code": 110, "label": "n" },
{ "code": 109, "label": "m" }
]
]
}

View File

@@ -0,0 +1,45 @@
{
"type": "characters",
"name": "colemak",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
{ "code": 113, "label": "q" },
{ "code": 119, "label": "w" },
{ "code": 102, "label": "f" },
{ "code": 112, "label": "p" },
{ "code": 103, "label": "g" },
{ "code": 106, "label": "j" },
{ "code": 108, "label": "l" },
{ "code": 117, "label": "u" },
{ "code": 121, "label": "y" },
{ "code": 59, "label": ";", "popup": {
"relevant": [
{ "code": 58, "label": ":" }
]
} }
],
[
{ "code": 97, "label": "a" },
{ "code": 114, "label": "r" },
{ "code": 115, "label": "s" },
{ "code": 116, "label": "t" },
{ "code": 100, "label": "d" },
{ "code": 104, "label": "h" },
{ "code": 110, "label": "n" },
{ "code": 101, "label": "e" },
{ "code": 105, "label": "i" },
{ "code": 111, "label": "o" }
],
[
{ "code": 122, "label": "z" },
{ "code": 120, "label": "x" },
{ "code": 99, "label": "c" },
{ "code": 118, "label": "v" },
{ "code": 98, "label": "b" },
{ "code": 107, "label": "k" },
{ "code": 109, "label": "m" }
]
]
}

View File

@@ -1,6 +1,7 @@
{
"type": "characters",
"name": "danish",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
@@ -15,7 +16,8 @@
{ "code": 111, "label": "o" },
{ "code": 112, "label": "p" },
{ "code": 229, "label": "å" }
], [
],
[
{ "code": 97, "label": "a" },
{ "code": 115, "label": "s" },
{ "code": 100, "label": "d" },
@@ -27,7 +29,8 @@
{ "code": 108, "label": "l" },
{ "code": 230, "label": "æ" },
{ "code": 248, "label": "ø" }
], [
],
[
{ "code": 122, "label": "z" },
{ "code": 120, "label": "x" },
{ "code": 99, "label": "c" },

View File

@@ -0,0 +1,64 @@
{
"type": "characters",
"name": "dvorak",
"authors": [ "patrickgold" ],
"direction": "ltr",
"modifier": "dvorak",
"arrangement": [
[
{ "code": 64, "label": "@", "groupId": 101, "variation": "email_address" },
{ "code": 39, "label": "'", "groupId": 101, "variation": "normal", "popup": {
"relevant": [
{ "code": 33, "label": "!" },
{ "code": 34, "label": "\"" }
]
} },
{ "code": 39, "label": "'", "groupId": 101, "variation": "password", "popup": {
"relevant": [
{ "code": 33, "label": "!" },
{ "code": 34, "label": "\"" }
]
} },
{ "code": 47, "label": "/", "groupId": 101, "variation": "uri" },
{ "code": 44, "label": ",", "popup": {
"relevant": [
{ "code": 60, "label": "<" },
{ "code": 63, "label": "?" }
]
} },
{ "code": 46, "label": ".", "popup": {
"relevant": [
{ "code": 62, "label": ">" }
]
} },
{ "code": 112, "label": "p" },
{ "code": 121, "label": "y" },
{ "code": 102, "label": "f" },
{ "code": 103, "label": "g" },
{ "code": 99, "label": "c" },
{ "code": 114, "label": "r" },
{ "code": 108, "label": "l" }
],
[
{ "code": 97, "label": "a" },
{ "code": 111, "label": "o" },
{ "code": 101, "label": "e" },
{ "code": 117, "label": "u" },
{ "code": 105, "label": "i" },
{ "code": 100, "label": "d" },
{ "code": 104, "label": "h" },
{ "code": 116, "label": "t" },
{ "code": 110, "label": "n" },
{ "code": 115, "label": "s" }
],
[
{ "code": 106, "label": "j" },
{ "code": 107, "label": "k" },
{ "code": 120, "label": "x" },
{ "code": 98, "label": "b" },
{ "code": 109, "label": "m" },
{ "code": 119, "label": "w" },
{ "code": 118, "label": "v" }
]
]
}

View File

@@ -0,0 +1,49 @@
{
"type": "characters",
"name": "esperanto",
"authors": [ "jeremiah-miller", "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
{ "code": 349, "label": "ŝ", "popup": {
"main": { "code": 113, "label": "q" }
} },
{ "code": 285, "label": "ĝ", "popup": {
"main": { "code": 119, "label": "w" }
} },
{ "code": 101, "label": "e" },
{ "code": 114, "label": "r" },
{ "code": 116, "label": "t" },
{ "code": 365, "label": "ŭ", "popup": {
"main": { "code": 121, "label": "y" }
} },
{ "code": 117, "label": "u" },
{ "code": 105, "label": "i" },
{ "code": 111, "label": "o" },
{ "code": 112, "label": "p" }
],
[
{ "code": 97, "label": "a" },
{ "code": 115, "label": "s" },
{ "code": 100, "label": "d" },
{ "code": 102, "label": "f" },
{ "code": 103, "label": "g" },
{ "code": 104, "label": "h" },
{ "code": 106, "label": "j" },
{ "code": 107, "label": "k" },
{ "code": 108, "label": "l" },
{ "code": 309, "label": "ĵ" }
],
[
{ "code": 122, "label": "z" },
{ "code": 265, "label": "ĉ", "popup": {
"main": { "code": 120, "label": "x" }
} },
{ "code": 99, "label": "c" },
{ "code": 118, "label": "v" },
{ "code": 98, "label": "b" },
{ "code": 110, "label": "n" },
{ "code": 109, "label": "m" }
]
]
}

View File

@@ -0,0 +1,50 @@
{
"type": "characters",
"name": "esperanto_with_hx",
"authors": [ "jeremiah-miller", "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
{ "code": 349, "label": "ŝ", "popup": {
"main": { "code": 113, "label": "q" }
} },
{ "code": 285, "label": "ĝ", "popup": {
"main": { "code": 119, "label": "w" }
} },
{ "code": 101, "label": "e" },
{ "code": 114, "label": "r" },
{ "code": 116, "label": "t" },
{ "code": 365, "label": "ŭ", "popup": {
"main": { "code": 121, "label": "y" }
} },
{ "code": 117, "label": "u" },
{ "code": 105, "label": "i" },
{ "code": 111, "label": "o" },
{ "code": 112, "label": "p" }
],
[
{ "code": 97, "label": "a" },
{ "code": 115, "label": "s" },
{ "code": 100, "label": "d" },
{ "code": 102, "label": "f" },
{ "code": 103, "label": "g" },
{ "code": 104, "label": "h" },
{ "code": 106, "label": "j" },
{ "code": 107, "label": "k" },
{ "code": 108, "label": "l" },
{ "code": 309, "label": "ĵ" }
],
[
{ "code": 122, "label": "z" },
{ "code": 265, "label": "ĉ", "popup": {
"main": { "code": 120, "label": "x" }
} },
{ "code": 99, "label": "c" },
{ "code": 118, "label": "v" },
{ "code": 98, "label": "b" },
{ "code": 110, "label": "n" },
{ "code": 109, "label": "m" },
{ "code": 293, "label": "ĥ" }
]
]
}

View File

@@ -0,0 +1,22 @@
{
"type": "characters/extended_popups",
"name": "$default",
"authors": [ "patrickgold" ],
"mapping": {
"all": {
"~enter": {
"main": { "code": -215, "label": "toggle_one_handed_mode", "type": "system_gui" },
"relevant": [
{ "code": -213, "label": "switch_to_media_context", "type": "system_gui" }
]
},
"~left": {
"main": { "code": -213, "label": "switch_to_media_context", "type": "system_gui" },
"relevant": [
{ "code": -215, "label": "toggle_one_handed_mode", "type": "system_gui" },
{ "code": -100, "label": "settings", "type": "system_gui" }
]
}
}
}
}

View File

@@ -0,0 +1,148 @@
{
"type": "characters/extended_popups",
"name": "ar",
"authors": [ "HeiWiper" ],
"mapping": {
"all": {
"ض": {
"relevant": [
{ "code": 1633, "label": "١" }
]
},
"ص": {
"relevant": [
{ "code": 1634, "label": "٢" }
]
},
"ث": {
"relevant": [
{ "code": 1635, "label": "٣" }
]
},
"ق": {
"relevant": [
{ "code": 1704, "label": "ڨ" },
{ "code": 1636, "label": "٤" }
]
},
"ف": {
"relevant": [
{ "code": 1701, "label": "ڥ" },
{ "code": 1700, "label": "ڤ" },
{ "code": 1698, "label": "ڢ" },
{ "code": 1637, "label": "٥" }
]
},
"غ": {
"relevant": [
{ "code": 1638, "label": "٦" }
]
},
"ع": {
"relevant": [
{ "code": 1639, "label": "٧" }
]
},
"ه": {
"relevant": [
{ "code": 1726, "label": "ھ" },
{ "code": 1640, "label": "٨" }
]
},
"خ": {
"relevant": [
{ "code": 1641, "label": "٩" }
]
},
"ح": {
"relevant": [
{ "code": 1632, "label": "٠" }
]
},
"ج": {
"relevant": [
{ "code": 1670, "label": "چ" }
]
},
"ش": {
"relevant": [
{ "code": 1692, "label": "ڜ" }
]
},
"ي": {
"relevant": [
{ "code": 1574, "label": "ئ" },
{ "code": 1609, "label": "ى" }
]
},
"ب": {
"relevant": [
{ "code": 1662, "label": "پ" }
]
},
"ل": {
"relevant": [
{ "code": 65275, "label": "لا" },
{ "code": 65273, "label": "لإ" },
{ "code": 65271, "label": "لأ" },
{ "code": 65269, "label": "لآ" }
]
},
"ا": {
"relevant": [
{ "code": 1570, "label": "آ" },
{ "code": 1569, "label": "ء" },
{ "code": 1571, "label": "أ" },
{ "code": 1573, "label": "إ" },
{ "code": 1649, "label": "ٱ" }
]
},
"ك": {
"relevant": [
{ "code": 1705, "label": "ک"},
{ "code": 1711, "label": "گ" }
]
},
"ى": {
"relevant": [
{ "code": 1574, "label": "ئ" }
]
},
"ز": {
"relevant": [
{ "code": 1688, "label": "ژ" }
]
},
"~right": {
"main": { "code": 1611, "label": "ً" },
"relevant": [
{ "code": 1622, "label": "ٖ" },
{ "code": 1648, "label": "ٰ" },
{ "code": 1619, "label": "ٓ" },
{ "code": 1615, "label": "ُ" },
{ "code": 1616, "label": "ِ" },
{ "code": 1614, "label": "َ" },
{ "code": 1600, "label": "ـ" },
{ "code": 1621, "label": "ٕ" },
{ "code": 1620, "label": "ٔ" },
{ "code": 1617, "label": "ّ" },
{ "code": 1612, "label": "ٌ" },
{ "code": 1613, "label": "ٍ" },
{ "code": 1618, "label": "ْ" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".ir"},
"relevant": [
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".com" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
}
}
}
}

View File

@@ -1,96 +1,133 @@
{
"a": [
{ "code": 229, "label": "å" },
{ "code": 224, "label": " },
{ "code": 226, "label": "â" },
{ "code": 227, "label": "ã" },
{ "code": 257, "label": "ā" },
{ "code": 230, "label": "æ" },
{ "code": 225, "label": "á" },
{ "code": 228, "label": "ä" }
],
"d": [
{ "code": 240, "label": "ð" }
],
"e": [
{ "code": 233, "label": "é" },
{ "code": 275, "label": "ē" },
{ "code": 281, "label": "ę" },
{ "code": 279, "label": "ė" },
{ "code": 232, "label": "è" },
{ "code": 235, "label": "ë" },
{ "code": 234, "label": "ê" }
],
"i": [
{ "code": 237, "label": "í" },
{ "code": 299, "label": "ī" },
{ "code": 236, "label": "ì" },
{ "code": 303, "label": "į" },
{ "code": 238, "label": "î" },
{ "code": 239, "label": "ï" }
],
"l": [
{ "code": 322, "label": "ł" }
],
"n": [
{ "code": 241, "label": "ñ" },
{ "code": 324, "label": "ń" }
],
"o": [
{ "code": 248, "label": "ø" },
{ "code": 333, "label": "ō" },
{ "code": 339, "label": "œ" },
{ "code": 242, "label": "ò" },
{ "code": 245, "label": "õ" },
{ "code": 244, "label": "ô" },
{ "code": 243, "label": "ó" },
{ "code": 246, "label": "ö" }
],
"s": [
{ "code": 223, "label": "ß" },
{ "code": 347, "label": "ś" },
{ "code": 353, "label": "š" }
],
"u": [
{ "code": 250, "label": "ú" },
{ "code": 363, "label": "ū" },
{ "code": 251, "label": "û" },
{ "code": 252, "label": "ü" },
{ "code": 249, "label": "ù" }
],
"y": [
{ "code": 253, "label": "ý" },
{ "code": 255, "label": "ÿ" }
],
"æ": [
{ "code": 228, "label": "ä" }
],
"ø": [
{ "code": 246, "label": "ö" }
],
".~normal": [
{ "code": 44, "label": "," },
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
],
".~uri": [
{ "code": -255, "label": ".com" },
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
"type": "characters/extended_popups",
"name": "da",
"authors": [ "patrickgold" ],
"mapping": {
"all": {
"a": {
"relevant": [
{ "code": 224, "label": "à" },
{ "code": 226, "label": "â" },
{ "code": 227, "label": "ã" },
{ "code": 257, "label": "ā" },
{ "code": 229, "label": "å" },
{ "code": 230, "label": "æ" },
{ "code": 225, "label": "á" },
{ "code": 228, "label": "ä" }
]
},
"d": {
"relevant": [
{ "code": 240, "label": "ð" }
]
},
"e": {
"relevant": [
{ "code": 233, "label": "é" },
{ "code": 275, "label": "ē" },
{ "code": 281, "label": "ę" },
{ "code": 279, "label": "ė" },
{ "code": 232, "label": "è" },
{ "code": 235, "label": "ë" },
{ "code": 234, "label": "ê" }
]
},
"i": {
"relevant": [
{ "code": 237, "label": "í" },
{ "code": 299, "label": "ī" },
{ "code": 236, "label": "ì" },
{ "code": 303, "label": "į" },
{ "code": 238, "label": "î" },
{ "code": 239, "label": "ï" }
]
},
"l": {
"relevant": [
{ "code": 322, "label": "ł" }
]
},
"n": {
"relevant": [
{ "code": 241, "label": "ñ" },
{ "code": 324, "label": "ń" }
]
},
"o": {
"relevant": [
{ "code": 248, "label": "ø" },
{ "code": 333, "label": "ō" },
{ "code": 339, "label": "œ" },
{ "code": 242, "label": "ò" },
{ "code": 245, "label": "õ" },
{ "code": 244, "label": "ô" },
{ "code": 243, "label": "ó" },
{ "code": 246, "label": "ö" }
]
},
"s": {
"relevant": [
{ "code": 223, "label": "ß" },
{ "code": 347, "label": "ś" },
{ "code": 353, "label": "š" }
]
},
"u": {
"relevant": [
{ "code": 363, "label": "ū" },
{ "code": 251, "label": "û" },
{ "code": 250, "label": "ú" },
{ "code": 252, "label": "ü" },
{ "code": 249, "label": "ù" }
]
},
"y": {
"relevant": [
{ "code": 253, "label": "ý" },
{ "code": 255, "label": "ÿ" }
]
},
"æ": {
"relevant": [
{ "code": 228, "label": "ä" }
]
},
"ø": {
"relevant": [
{ "code": 246, "label": "ö" }
]
},
"~right": {
"main": { "code": 44, "label": "," },
"relevant": [
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".com" },
"relevant": [
{ "code": -255, "label": ".eu" },
{ "code": -255, "label": ".dk" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
}
}
}
}

View File

@@ -1,80 +1,109 @@
{
"a": [
{ "code": 228, "label": "ä" },
{ "code": 230, "label": " },
{ "code": 227, "label": "ã" },
{ "code": 229, "label": "å" },
{ "code": 257, "label": "ā" },
{ "code": 226, "label": "â" },
{ "code": 224, "label": "à" },
{ "code": 225, "label": "á" }
],
"c": [
{ "code": 231, "label": "ç" }
],
"e": [
{ "code": 233, "label": "é" },
{ "code": 275, "label": "ē" },
{ "code": 234, "label": "ê" },
{ "code": 232, "label": "è" },
{ "code": 235, "label": "ë" }
],
"i": [
{ "code": 237, "label": "í" },
{ "code": 236, "label": "ì" },
{ "code": 239, "label": "ï" },
{ "code": 238, "label": "î" },
{ "code": 299, "label": "ī" }
],
"n": [
{ "code": 241, "label": "ñ" },
{ "code": 324, "label": "ń" }
],
"o": [
{ "code": 246, "label": "ö" },
{ "code": 333, "label": "ō" },
{ "code": 248, "label": "ø" },
{ "code": 245, "label": "õ" },
{ "code": 339, "label": "œ" },
{ "code": 243, "label": "ó" },
{ "code": 242, "label": "ò" },
{ "code": 244, "label": "ô" }
],
"s": [
{ "code": 223, "label": "ß" },
{ "code": 353, "label": "š" },
{ "code": 347, "label": "ś" }
],
"u": [
{ "code": 252, "label": "ü" },
{ "code": 363, "label": "ū" },
{ "code": 249, "label": "ù" },
{ "code": 251, "label": "û" },
{ "code": 250, "label": "ú" }
],
".~normal": [
{ "code": 44, "label": "," },
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
],
".~uri": [
{ "code": -255, "label": ".com" },
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
"type": "characters/extended_popups",
"name": "de",
"authors": [ "patrickgold" ],
"mapping": {
"all": {
"a": {
"main": { "code": 228, "label": "ä" },
"relevant": [
{ "code": 230, "label": "æ" },
{ "code": 227, "label": "ã" },
{ "code": 229, "label": "å" },
{ "code": 257, "label": "ā" },
{ "code": 226, "label": "â" },
{ "code": 224, "label": "à" },
{ "code": 225, "label": "á" }
]
},
"c": {
"relevant": [
{ "code": 231, "label": "ç" }
]
},
"e": {
"relevant": [
{ "code": 233, "label": "é" },
{ "code": 275, "label": "ē" },
{ "code": 234, "label": "ê" },
{ "code": 232, "label": "è" },
{ "code": 235, "label": "ë" }
]
},
"i": {
"relevant": [
{ "code": 237, "label": "í" },
{ "code": 236, "label": "ì" },
{ "code": 239, "label": "ï" },
{ "code": 238, "label": "î" },
{ "code": 299, "label": "ī" }
]
},
"n": {
"relevant": [
{ "code": 241, "label": "ñ" },
{ "code": 324, "label": "ń" }
]
},
"o": {
"main": { "code": 246, "label": "ö" },
"relevant": [
{ "code": 333, "label": "ō" },
{ "code": 248, "label": "ø" },
{ "code": 245, "label": "õ" },
{ "code": 339, "label": "œ" },
{ "code": 243, "label": "ó" },
{ "code": 242, "label": "ò" },
{ "code": 244, "label": "ô" }
]
},
"s": {
"main": { "code": 223, "label": "ß" },
"relevant": [
{ "code": 353, "label": "š" },
{ "code": 347, "label": "ś" }
]
},
"u": {
"main": { "code": 252, "label": "ü" },
"relevant": [
{ "code": 363, "label": "ū" },
{ "code": 249, "label": "ù" },
{ "code": 251, "label": "û" },
{ "code": 250, "label": "ú" }
]
},
"~right": {
"main": { "code": 44, "label": "," },
"relevant": [
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".com" },
"relevant": [
{ "code": -255, "label": ".ch" },
{ "code": -255, "label": ".de" },
{ "code": -255, "label": ".at" },
{ "code": -255, "label": ".net" }
]
}
}
}
}

View File

@@ -0,0 +1,80 @@
{
"type": "characters/extended_popups",
"name": "el",
"authors": [ "tsiflimagas" ],
"mapping": {
"all": {
"α": {
"relevant": [
{ "code": 940, "label": "ά" }
]
},
"ε": {
"relevant": [
{ "code": 941, "label": "έ" }
]
},
"η": {
"relevant": [
{ "code": 942, "label": "ή" }
]
},
"ι": {
"relevant": [
{ "code": 912, "label": "ΐ" },
{ "code": 970, "label": "ϊ" },
{ "code": 943, "label": "ί" }
]
},
"ο": {
"relevant": [
{ "code": 972, "label": "ό" }
]
},
"υ": {
"relevant": [
{ "code": 944, "label": "ΰ" },
{ "code": 971, "label": "ϋ" },
{ "code": 973, "label": "ύ" }
]
},
"ω": {
"relevant": [
{ "code": 974, "label": "ώ" }
]
},
"~right": {
"main": { "code": 44, "label": "," },
"relevant": [
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".com" },
"relevant": [
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".gr" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
}
}
}
}

View File

@@ -1,78 +1,107 @@
{
"a": [
{ "code": 224, "label": "à" },
{ "code": 230, "label": " },
{ "code": 227, "label": "ã" },
{ "code": 229, "label": "å" },
{ "code": 257, "label": "ā" },
{ "code": 225, "label": "á" },
{ "code": 226, "label": "â" },
{ "code": 228, "label": "ä" }
],
"c": [
{ "code": 231, "label": "ç" }
],
"e": [
{ "code": 233, "label": "é" },
{ "code": 275, "label": "ē" },
{ "code": 234, "label": "ê" },
{ "code": 232, "label": "è" },
{ "code": 235, "label": "ë" }
],
"i": [
{ "code": 237, "label": "í" },
{ "code": 236, "label": "ì" },
{ "code": 239, "label": "ï" },
{ "code": 238, "label": "î" },
{ "code": 299, "label": "ī" }
],
"n": [
{ "code": 241, "label": "ñ" },
{ "code": 324, "label": "ń" }
],
"o": [
{ "code": 243, "label": "ó" },
{ "code": 245, "label": "õ" },
{ "code": 333, "label": "ō" },
{ "code": 339, "label": "œ" },
{ "code": 248, "label": "ø" },
{ "code": 242, "label": "ò" },
{ "code": 246, "label": "ö" },
{ "code": 244, "label": "ô" }
],
"s": [
{ "code": 223, "label": "ß" }
],
"u": [
{ "code": 250, "label": "ú" },
{ "code": 363, "label": "ū" },
{ "code": 252, "label": "ü" },
{ "code": 251, "label": "û" },
{ "code": 249, "label": "ù" }
],
".~normal": [
{ "code": 44, "label": "," },
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
],
".~uri": [
{ "code": -255, "label": ".com" },
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
"type": "characters/extended_popups",
"name": "en",
"authors": [ "patrickgold" ],
"mapping": {
"all": {
"a": {
"relevant": [
{ "code": 230, "label": "æ" },
{ "code": 227, "label": "ã" },
{ "code": 229, "label": "å" },
{ "code": 257, "label": "ā" },
{ "code": 224, "label": "à" },
{ "code": 225, "label": "á" },
{ "code": 226, "label": "â" },
{ "code": 228, "label": "ä" }
]
},
"c": {
"relevant": [
{ "code": 231, "label": "ç" }
]
},
"e": {
"relevant": [
{ "code": 275, "label": "ē" },
{ "code": 234, "label": "ê" },
{ "code": 233, "label": "é" },
{ "code": 232, "label": "è" },
{ "code": 235, "label": "ë" }
]
},
"i": {
"relevant": [
{ "code": 236, "label": "ì" },
{ "code": 239, "label": "ï" },
{ "code": 237, "label": "í" },
{ "code": 238, "label": "î" },
{ "code": 299, "label": "ī" }
]
},
"n": {
"relevant": [
{ "code": 241, "label": "ñ" },
{ "code": 324, "label": "ń" }
]
},
"o": {
"relevant": [
{ "code": 245, "label": "õ" },
{ "code": 333, "label": "ō" },
{ "code": 339, "label": "œ" },
{ "code": 248, "label": "ø" },
{ "code": 242, "label": "ò" },
{ "code": 246, "label": "ö" },
{ "code": 243, "label": "ó" },
{ "code": 244, "label": "ô" }
]
},
"s": {
"relevant": [
{ "code": 223, "label": "ß" }
]
},
"u": {
"relevant": [
{ "code": 250, "label": "ú" },
{ "code": 363, "label": "ū" },
{ "code": 252, "label": "ü" },
{ "code": 251, "label": "û" },
{ "code": 249, "label": "ù" }
]
},
"~right": {
"main": { "code": 44, "label": "," },
"relevant": [
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".com" },
"relevant": [
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
}
}
}
}

View File

@@ -0,0 +1,90 @@
{
"type": "characters/extended_popups",
"name": "eo",
"authors": [ "jeremiah-miller" ],
"mapping":{
"all": {
"c": {
"relevant": [
{ "code": 265, "label": "ĉ" }
]
},
"g": {
"relevant": [
{ "code": 285, "label": "ĝ" }
]
},
"h": {
"relevant": [
{ "code": 293, "label": "ĥ" }
]
},
"j": {
"relevant": [
{ "code": 309, "label": "ĵ" }
]
},
"s": {
"relevant": [
{ "code": 349, "label": "ŝ" }
]
},
"u": {
"relevant": [
{ "code": 365, "label": "ŭ" }
]
},
"q": {
"relevant": [
{ "code": 349, "label": "ŝ" }
]
},
"w": {
"relevant": [
{ "code": 285, "label": "ĝ" }
]
},
"x": {
"relevant": [
{ "code": 265, "label": "ĉ" }
]
},
"y": {
"relevant": [
{ "code": 365, "label": "ŭ" }
]
},
"~right": {
"main": { "code": 44, "label": "," },
"relevant": [
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".com" },
"relevant": [
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
}
}
}
}

View File

@@ -1,86 +1,115 @@
{
"a": [
{ "code": 225, "label": "á" },
{ "code": 229, "label": " },
{ "code": 261, "label": "ą" },
{ "code": 230, "label": "æ" },
{ "code": 257, "label": "ā" },
{ "code": 170, "label": "ª" },
{ "code": 224, "label": "à" },
{ "code": 228, "label": "ä" },
{ "code": 226, "label": "â" },
{ "code": 227, "label": "ã" }
],
"c": [
{ "code": 231, "label": "ç" },
{ "code": 269, "label": "č" },
{ "code": 263, "label": "ć" }
],
"e": [
{ "code": 233, "label": "é" },
{ "code": 275, "label": "ē" },
{ "code": 281, "label": "ę" },
{ "code": 279, "label": "ė" },
{ "code": 235, "label": "ë" },
{ "code": 232, "label": "è" },
{ "code": 234, "label": "ê" }
],
"i": [
{ "code": 237, "label": "í" },
{ "code": 299, "label": "ī" },
{ "code": 238, "label": "î" },
{ "code": 303, "label": "į" },
{ "code": 236, "label": "ì" },
{ "code": 239, "label": "ï" }
],
"n": [
{ "code": 241, "label": "ñ" },
{ "code": 324, "label": "ń" }
],
"o": [
{ "code": 243, "label": "ó" },
{ "code": 186, "label": "º" },
{ "code": 333, "label": "ō" },
{ "code": 248, "label": "ø" },
{ "code": 339, "label": "œ" },
{ "code": 245, "label": "õ" },
{ "code": 244, "label": "ô" },
{ "code": 246, "label": "ö" },
{ "code": 242, "label": "ò" }
],
"s": [
{ "code": 223, "label": "ß" }
],
"u": [
{ "code": 250, "label": "ú" },
{ "code": 363, "label": "ū" },
{ "code": 249, "label": "ù" },
{ "code": 252, "label": "ü" },
{ "code": 251, "label": "û" }
],
".~normal": [
{ "code": 44, "label": "," },
{ "code": 58, "label": ":" },
{ "code": 38, "label": "&" },
{ "code": 64, "label": "@" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 161, "label": "¡" },
{ "code": 39, "label": "'" },
{ "code": 191, "label": "¿" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
],
".~uri": [
{ "code": -255, "label": ".com" },
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
"type": "characters/extended_popups",
"name": "es",
"authors": [ "patrickgold" ],
"mapping": {
"all": {
"a": {
"main": { "code": 225, "label": "á" },
"relevant": [
{ "code": 229, "label": "å" },
{ "code": 261, "label": "ą" },
{ "code": 230, "label": "æ" },
{ "code": 257, "label": "ā" },
{ "code": 170, "label": "ª" },
{ "code": 224, "label": "à" },
{ "code": 228, "label": "ä" },
{ "code": 226, "label": "â" },
{ "code": 227, "label": "ã" }
]
},
"c": {
"relevant": [
{ "code": 269, "label": "č" },
{ "code": 231, "label": "ç" },
{ "code": 263, "label": "ć" }
]
},
"e": {
"relevant": [
{ "code": 275, "label": "ē" },
{ "code": 281, "label": "ę" },
{ "code": 279, "label": "ė" },
{ "code": 235, "label": "ë" },
{ "code": 233, "label": "é" },
{ "code": 232, "label": "è" },
{ "code": 234, "label": "ê" }
]
},
"i": {
"relevant": [
{ "code": 299, "label": "ī" },
{ "code": 238, "label": "î" },
{ "code": 303, "label": "į" },
{ "code": 236, "label": "ì" },
{ "code": 237, "label": "í" },
{ "code": 239, "label": "ï" }
]
},
"n": {
"relevant": [
{ "code": 241, "label": "ñ" },
{ "code": 324, "label": "ń" }
]
},
"o": {
"main": { "code": 243, "label": "ó" },
"relevant": [
{ "code": 186, "label": "º" },
{ "code": 333, "label": "ō" },
{ "code": 248, "label": "ø" },
{ "code": 339, "label": "œ" },
{ "code": 245, "label": "õ" },
{ "code": 244, "label": "ô" },
{ "code": 246, "label": "ö" },
{ "code": 242, "label": "ò" }
]
},
"s": {
"relevant": [
{ "code": 223, "label": "ß" }
]
},
"u": {
"relevant": [
{ "code": 250, "label": "ú" },
{ "code": 363, "label": "ū" },
{ "code": 249, "label": "ù" },
{ "code": 252, "label": "ü" },
{ "code": 251, "label": "û" }
]
},
"~right": {
"main": { "code": 44, "label": "," },
"relevant": [
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".com" },
"relevant": [
{ "code": -255, "label": ".com.es" },
{ "code": -255, "label": ".es" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
}
}
}
}

View File

@@ -1,80 +1,125 @@
{
"ض": [
{ "code": 1777, "label": "۱" }
],
"ص": [
{ "code": 1778, "label": "۲" }
],
": [
{ "code": 1779, "label": "۳" }
],
"ق": [
{ "code": 1780, "label": "۴" }
],
"ف": [
{ "code": 1781, "label": "۵" }
],
"غ": [
{ "code": 1782, "label": "۶" }
],
"ع": [
{ "code": 1783, "label": "۷" }
],
"ه": [
{ "code": 1784, "label": "۸" }
],
"خ": [
{ "code": 1785, "label": "۹" }
],
"ح": [
{ "code": 1776, "label": "۰" }
],
"ی": [
{ "code": 1574, "label": "ئ" },
{ "code": 1610, "label": "ي" }
],
"ا": [
{ "code": 1570, "label": "آ" },
{ "code": 1649, "label": "ٱ" },
{ "code": 1569, "label": "ء" },
{ "code": 1571, "label": "أ" },
{ "code": 1573, "label": "إ" }
],
": [
{ "code": 1577, "label": "ة" }
],
"ک": [
{ "code": 1706, "label": "ڪ"},
{ "code": 1603, "label": "ك" }
],
"ز": [
{ "code": 1688, "label": "ژ" }
],
": [
{ "code": 1572, "label": "ؤ" }
],
".~normal": [
{ "code": 1611, "label": "ً" },
{ "code": 1622, "label": "ٖ" },
{ "code": 1648, "label": "ٰ" },
{ "code": 1619, "label": "ٓ" },
{ "code": 1615, "label": "ُ" },
{ "code": 1616, "label": "ِ" },
{ "code": 1614, "label": "َ" },
{ "code": 1600, "label": "ـ" },
{ "code": 1621, "label": "ٕ" },
{ "code": 1618, "label": "ْ" },
{ "code": 1617, "label": "ّ" },
{ "code": 1612, "label": "ٌ" },
{ "code": 1613, "label": "ٍ" },
{ "code": 1620, "label": "ٔ" }
],
".~uri": [
{ "code": -255, "label": ".ir"},
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".com" },
{ "code": -255, "label": ".net" }
]
"type": "characters/extended_popups",
"name": "fa",
"authors": [ "PHELAT" ],
"mapping": {
"all": {
"ض": {
"relevant": [
{ "code": 1777, "label": "۱" }
]
},
"ص": {
"relevant": [
{ "code": 1778, "label": "۲" }
]
},
"ث": {
"relevant": [
{ "code": 1779, "label": "۳" }
]
},
"ق": {
"relevant": [
{ "code": 1780, "label": "۴" }
]
},
"ف": {
"relevant": [
{ "code": 1781, "label": "۵" }
]
},
"غ": {
"relevant": [
{ "code": 1782, "label": "۶" }
]
},
"ع": {
"relevant": [
{ "code": 1783, "label": "۷" }
]
},
"ه": {
"relevant": [
{ "code": 1784, "label": "۸" }
]
},
"خ": {
"relevant": [
{ "code": 1785, "label": "۹" }
]
},
"ح": {
"relevant": [
{ "code": 1776, "label": "۰" }
]
},
"ی": {
"relevant": [
{ "code": 1574, "label": "ئ" },
{ "code": 1610, "label": "ي" }
]
},
"ا": {
"relevant": [
{ "code": 1570, "label": "آ" },
{ "code": 1649, "label": "ٱ" },
{ "code": 1569, "label": "ء" },
{ "code": 1571, "label": "أ" },
{ "code": 1573, "label": "إ" }
]
},
": {
"relevant": [
{ "code": 1577, "label": "ة" }
]
},
"ک": {
"relevant": [
{ "code": 1706, "label": "ڪ"},
{ "code": 1603, "label": "ك" }
]
},
"ز": {
"relevant": [
{ "code": 1688, "label": "ژ" }
]
},
"و": {
"relevant": [
{ "code": 1572, "label": "ؤ" }
]
},
"~right": {
"main": { "code": 1611, "label": "ً" },
"relevant": [
{ "code": 1622, "label": "ٖ" },
{ "code": 1648, "label": "ٰ" },
{ "code": 1619, "label": "ٓ" },
{ "code": 1615, "label": "ُ" },
{ "code": 1616, "label": "ِ" },
{ "code": 1614, "label": "َ" },
{ "code": 1600, "label": "ـ" },
{ "code": 1621, "label": "ٕ" },
{ "code": 1618, "label": "ْ" },
{ "code": 1617, "label": "ّ" },
{ "code": 1612, "label": "ٌ" },
{ "code": 1613, "label": "ٍ" },
{ "code": 1620, "label": "ٔ" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".ir"},
"relevant": [
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".com" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
}
}
}
}

View File

@@ -1,87 +1,118 @@
{
"a": [
{ "code": 228, "label": "ä" },
{ "code": 225, "label": " },
{ "code": 226, "label": "â" },
{ "code": 227, "label": "ã" },
{ "code": 257, "label": "ā" },
{ "code": 229, "label": "å" },
{ "code": 230, "label": "æ" },
{ "code": 224, "label": "à" }
],
"e": [
{ "code": 233, "label": "é" },
{ "code": 275, "label": "ē" },
{ "code": 281, "label": "ę" },
{ "code": 279, "label": "ė" },
{ "code": 232, "label": "è" },
{ "code": 235, "label": "ë" },
{ "code": 234, "label": "ê" }
],
"i": [
{ "code": 237, "label": "í" },
{ "code": 299, "label": "ī" },
{ "code": 236, "label": "ì" },
{ "code": 303, "label": "į" },
{ "code": 238, "label": "î" },
{ "code": 239, "label": "ï" }
],
"o": [
{ "code": 246, "label": "ö" },
{ "code": 333, "label": "ō" },
{ "code": 339, "label": "œ" },
{ "code": 243, "label": "ó" },
{ "code": 245, "label": "õ" },
{ "code": 242, "label": "ò" },
{ "code": 244, "label": "ô" },
{ "code": 248, "label": "ø" }
],
"s": [
{ "code": 353, "label": "š" },
{ "code": 223, "label": "ß" },
{ "code": 347, "label": "ś" }
],
"u": [
{ "code": 252, "label": "ü" },
{ "code": 363, "label": "ū" },
{ "code": 251, "label": "û" },
{ "code": 250, "label": "ú" },
{ "code": 249, "label": "ù" }
],
"z": [
{ "code": 382, "label": "ž" },
{ "code": 380, "label": "ż" },
{ "code": 378, "label": "ź" }
],
"ä": [
{ "code": 230, "label": "æ" }
],
"ö": [
{ "code": 248, "label": "ø" }
],
".~normal": [
{ "code": 44, "label": "," },
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
],
".~uri": [
{ "code": -255, "label": ".com" },
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
"type": "characters/extended_popups",
"name": "fi",
"authors": [ "patrickgold" ],
"mapping": {
"all": {
"a": {
"relevant": [
{ "code": 228, "label": "ä" },
{ "code": 225, "label": "á" },
{ "code": 226, "label": "â" },
{ "code": 227, "label": "ã" },
{ "code": 257, "label": "ā" },
{ "code": 229, "label": "å" },
{ "code": 230, "label": "æ" },
{ "code": 224, "label": "à" }
]
},
"e": {
"relevant": [
{ "code": 233, "label": "é" },
{ "code": 275, "label": "ē" },
{ "code": 281, "label": "ę" },
{ "code": 279, "label": "ė" },
{ "code": 232, "label": "è" },
{ "code": 235, "label": "ë" },
{ "code": 234, "label": "ê" }
]
},
"i": {
"relevant": [
{ "code": 237, "label": "í" },
{ "code": 299, "label": "ī" },
{ "code": 236, "label": "ì" },
{ "code": 303, "label": "į" },
{ "code": 238, "label": "î" },
{ "code": 239, "label": "ï" }
]
},
"o": {
"relevant": [
{ "code": 246, "label": "ö" },
{ "code": 333, "label": "ō" },
{ "code": 339, "label": "œ" },
{ "code": 243, "label": "ó" },
{ "code": 245, "label": "õ" },
{ "code": 242, "label": "ò" },
{ "code": 244, "label": "ô" },
{ "code": 248, "label": "ø" }
]
},
"s": {
"relevant": [
{ "code": 353, "label": "š" },
{ "code": 223, "label": "ß" },
{ "code": 347, "label": "ś" }
]
},
"u": {
"relevant": [
{ "code": 252, "label": "ü" },
{ "code": 363, "label": "ū" },
{ "code": 251, "label": "û" },
{ "code": 250, "label": "ú" },
{ "code": 249, "label": "ù" }
]
},
"z": {
"relevant": [
{ "code": 382, "label": "ž" },
{ "code": 380, "label": "ż" },
{ "code": 378, "label": "ź" }
]
},
"ä": {
"relevant": [
{ "code": 230, "label": "æ" }
]
},
": {
"relevant": [
{ "code": 248, "label": "ø" }
]
},
"~right": {
"main": { "code": 44, "label": "," },
"relevant": [
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".com" },
"relevant": [
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
}
}
}
}

View File

@@ -1,90 +1,121 @@
{
"a": [
{ "code": 224, "label": "à" },
{ "code": 227, "label": " },
{ "code": 229, "label": "å" },
{ "code": 257, "label": "ā" },
{ "code": 170, "label": "ª" },
{ "code": 226, "label": "â" },
{ "code": 230, "label": "æ" },
{ "code": 225, "label": "á" },
{ "code": 228, "label": "ä" }
],
"c": [
{ "code": 231, "label": "ç" },
{ "code": 269, "label": "č" },
{ "code": 263, "label": "ć" }
],
"e": [
{ "code": 233, "label": "é" },
{ "code": 275, "label": "ē" },
{ "code": 281, "label": "ę" },
{ "code": 279, "label": "ė" },
{ "code": 234, "label": "ê" },
{ "code": 232, "label": "è" },
{ "code": 235, "label": "ë" }
],
"i": [
{ "code": 238, "label": "î" },
{ "code": 299, "label": "ī" },
{ "code": 237, "label": "í" },
{ "code": 303, "label": "į" },
{ "code": 236, "label": "ì" },
{ "code": 239, "label": "ï" }
],
"n": [
{ "code": 241, "label": "ñ" },
{ "code": 324, "label": "ń" }
],
"o": [
{ "code": 244, "label": "ô" },
{ "code": 186, "label": "º" },
{ "code": 333, "label": "ō" },
{ "code": 245, "label": "õ" },
{ "code": 248, "label": "ø" },
{ "code": 243, "label": "ó" },
{ "code": 242, "label": "ò" },
{ "code": 246, "label": "ö" },
{ "code": 339, "label": "œ" }
],
"s": [
{ "code": 223, "label": "ß" },
{ "code": 353, "label": "š" },
{ "code": 347, "label": "ś" }
],
"u": [
{ "code": 249, "label": "ù" },
{ "code": 363, "label": "ū" },
{ "code": 252, "label": "ü" },
{ "code": 251, "label": "û" },
{ "code": 250, "label": "ú" }
],
"y": [
{ "code": 255, "label": "ÿ" }
],
".~normal": [
{ "code": 44, "label": "," },
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
],
".~uri": [
{ "code": -255, "label": ".com" },
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
"type": "characters/extended_popups",
"name": "fr",
"authors": [ "patrickgold" ],
"mapping": {
"all": {
"a": {
"relevant": [
{ "code": 224, "label": "à" },
{ "code": 227, "label": "ã" },
{ "code": 229, "label": "å" },
{ "code": 257, "label": "ā" },
{ "code": 170, "label": "ª" },
{ "code": 226, "label": "â" },
{ "code": 230, "label": "æ" },
{ "code": 225, "label": "á" },
{ "code": 228, "label": "ä" }
]
},
"c": {
"relevant": [
{ "code": 231, "label": "ç" },
{ "code": 269, "label": "č" },
{ "code": 263, "label": "ć" }
]
},
"e": {
"relevant": [
{ "code": 233, "label": "é" },
{ "code": 275, "label": "ē" },
{ "code": 281, "label": "ę" },
{ "code": 279, "label": "ė" },
{ "code": 234, "label": "ê" },
{ "code": 232, "label": "è" },
{ "code": 235, "label": "ë" }
]
},
"i": {
"relevant": [
{ "code": 238, "label": "î" },
{ "code": 299, "label": "ī" },
{ "code": 237, "label": "í" },
{ "code": 303, "label": "į" },
{ "code": 236, "label": "ì" },
{ "code": 239, "label": "ï" }
]
},
"n": {
"relevant": [
{ "code": 241, "label": "ñ" },
{ "code": 324, "label": "ń" }
]
},
"o": {
"relevant": [
{ "code": 244, "label": "ô" },
{ "code": 186, "label": "º" },
{ "code": 333, "label": "ō" },
{ "code": 245, "label": "õ" },
{ "code": 248, "label": "ø" },
{ "code": 243, "label": "ó" },
{ "code": 242, "label": "ò" },
{ "code": 246, "label": "ö" },
{ "code": 339, "label": "œ" }
]
},
"s": {
"relevant": [
{ "code": 223, "label": "ß" },
{ "code": 353, "label": "š" },
{ "code": 347, "label": "ś" }
]
},
"u": {
"relevant": [
{ "code": 249, "label": "ù" },
{ "code": 363, "label": "ū" },
{ "code": 252, "label": "ü" },
{ "code": 251, "label": "û" },
{ "code": 250, "label": "ú" }
]
},
"y": {
"relevant": [
{ "code": 255, "label": "ÿ" }
]
},
"~right": {
"main": { "code": 44, "label": "," },
"relevant": [
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".com" },
"relevant": [
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
}
}
}
}

View File

@@ -0,0 +1,61 @@
{
"type": "characters/extended_popups",
"name": "hr",
"authors": [ "hedidnothingwrong" ],
"mapping": {
"all": {
"c": {
"relevant": [
{ "code": 269, "label": "č" },
{ "code": 263, "label": "ć" }
]
},
"d": {
"relevant": [
{ "code": 273, "label": "đ" }
]
},
"s": {
"relevant": [
{ "code": 353, "label": "š" }
]
},
"z": {
"relevant": [
{ "code": 382, "label": "ž" }
]
},
"~right": {
"main": { "code": 44, "label": "," },
"relevant": [
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".com" },
"relevant": [
{ "code": -255, "label": ".eu" },
{ "code": -255, "label": ".hr" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
}
}
}
}

View File

@@ -0,0 +1,80 @@
{
"type": "characters/extended_popups",
"name": "hu",
"authors": [ "zoli111" ],
"mapping": {
"all": {
"a": {
"relevant": [
{ "code": 225, "label": "á" }
]
},
"e": {
"relevant": [
{ "code": 233, "label": "é" }
]
},
"i": {
"relevant": [
{ "code": 237, "label": "í" }
]
},
"o": {
"relevant": [
{ "code": 243, "label": "ó" },
{ "code": 246, "label": "ö" },
{ "code": 337, "label": "ő" }
]
},
"ö": {
"relevant": [
{ "code": 337, "label": "ő" }
]
},
"u": {
"relevant": [
{ "code": 250, "label": "ú" },
{ "code": 252, "label": "ü" },
{ "code": 369, "label": "ű" }
]
},
"ü": {
"relevant": [
{ "code": 369, "label": "ű" }
]
},
"~right": {
"main": { "code": 44, "label": "," },
"relevant": [
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".com" },
"relevant": [
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".hu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
}
}
}
}

View File

@@ -1,81 +1,110 @@
{
"a": [
{ "code": 225, "label": "á" },
{ "code": 224, "label": " },
{ "code": 226, "label": "â" },
{ "code": 227, "label": "ã" },
{ "code": 257, "label": "ā" },
{ "code": 228, "label": "ä" },
{ "code": 230, "label": "æ" },
{ "code": 229, "label": "å" }
],
"d": [
{ "code": 240, "label": "ð" }
],
"e": [
{ "code": 233, "label": "é" },
{ "code": 275, "label": "ē" },
{ "code": 281, "label": "ę" },
{ "code": 279, "label": "ė" },
{ "code": 232, "label": "è" },
{ "code": 235, "label": "ë" },
{ "code": 234, "label": "ê" }
],
"i": [
{ "code": 237, "label": "í" },
{ "code": 299, "label": "ī" },
{ "code": 236, "label": "ì" },
{ "code": 303, "label": "į" },
{ "code": 238, "label": "î" },
{ "code": 239, "label": "ï" }
],
"o": [
{ "code": 243, "label": "ó" },
{ "code": 333, "label": "ō" },
{ "code": 248, "label": "ø" },
{ "code": 245, "label": "õ" },
{ "code": 339, "label": "œ" },
{ "code": 242, "label": "ò" },
{ "code": 244, "label": "ô" },
{ "code": 246, "label": "ö" }
],
"t": [
{ "code": 254, "label": "þ" }
],
"u": [
{ "code": 250, "label": "ú" },
{ "code": 363, "label": "ū" },
{ "code": 251, "label": "û" },
{ "code": 252, "label": "ü" },
{ "code": 249, "label": "ù" }
],
"y": [
{ "code": 253, "label": "ý" },
{ "code": 255, "label": "ÿ" }
],
".~normal": [
{ "code": 44, "label": "," },
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
],
".~uri": [
{ "code": -255, "label": ".com" },
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
"type": "characters/extended_popups",
"name": "is",
"authors": [ "patrickgold" ],
"mapping": {
"all": {
"a": {
"relevant": [
{ "code": 225, "label": "á" },
{ "code": 224, "label": "à" },
{ "code": 226, "label": "â" },
{ "code": 227, "label": "ã" },
{ "code": 257, "label": "ā" },
{ "code": 228, "label": "ä" },
{ "code": 230, "label": "æ" },
{ "code": 229, "label": "å" }
]
},
"d": {
"relevant": [
{ "code": 240, "label": "ð" }
]
},
"e": {
"relevant": [
{ "code": 233, "label": "é" },
{ "code": 275, "label": "ē" },
{ "code": 281, "label": "ę" },
{ "code": 279, "label": "ė" },
{ "code": 232, "label": "è" },
{ "code": 235, "label": "ë" },
{ "code": 234, "label": "ê" }
]
},
"i": {
"relevant": [
{ "code": 237, "label": "í" },
{ "code": 299, "label": "ī" },
{ "code": 236, "label": "ì" },
{ "code": 303, "label": "į" },
{ "code": 238, "label": "î" },
{ "code": 239, "label": "ï" }
]
},
"o": {
"relevant": [
{ "code": 243, "label": "ó" },
{ "code": 333, "label": "ō" },
{ "code": 248, "label": "ø" },
{ "code": 245, "label": "õ" },
{ "code": 339, "label": "œ" },
{ "code": 242, "label": "ò" },
{ "code": 244, "label": "ô" },
{ "code": 246, "label": "ö" }
]
},
"t": {
"relevant": [
{ "code": 254, "label": "þ" }
]
},
"u": {
"relevant": [
{ "code": 250, "label": "ú" },
{ "code": 363, "label": "ū" },
{ "code": 251, "label": "û" },
{ "code": 252, "label": "ü" },
{ "code": 249, "label": "ù" }
]
},
"y": {
"relevant": [
{ "code": 253, "label": "ý" },
{ "code": 255, "label": "ÿ" }
]
},
"~right": {
"main": { "code": 44, "label": "," },
"relevant": [
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".com" },
"relevant": [
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
}
}
}
}

View File

@@ -1,77 +1,103 @@
{
"a": [
{ "code": 224, "label": "à" },
{ "code": 227, "label": " },
{ "code": 229, "label": "å" },
{ "code": 257, "label": "ā" },
{ "code": 170, "label": "ª" },
{ "code": 225, "label": "á" },
{ "code": 226, "label": "â" },
{ "code": 228, "label": "ä" },
{ "code": 230, "label": "æ" }
],
"e": [
{ "code": 232, "label": "è" },
{ "code": 275, "label": "ē" },
{ "code": 281, "label": "ę" },
{ "code": 279, "label": "ė" },
{ "code": 234, "label": "ê" },
{ "code": 233, "label": "é" },
{ "code": 235, "label": "ë" }
],
"i": [
{ "code": 236, "label": "ì" },
{ "code": 299, "label": "ī" },
{ "code": 239, "label": "ï" },
{ "code": 303, "label": "į" },
{ "code": 238, "label": "î" },
{ "code": 237, "label": "í" }
],
"n": [
{ "code": 241, "label": "ñ" },
{ "code": 324, "label": "ń" }
],
"o": [
{ "code": 242, "label": "ò" },
{ "code": 186, "label": "º" },
{ "code": 333, "label": "ō" },
{ "code": 339, "label": "œ" },
{ "code": 248, "label": "ø" },
{ "code": 245, "label": "õ" },
{ "code": 246, "label": "ö" },
{ "code": 244, "label": "ô" },
{ "code": 243, "label": "ó" }
],
"u": [
{ "code": 249, "label": "ù" },
{ "code": 363, "label": "ū" },
{ "code": 251, "label": "û" },
{ "code": 250, "label": "ú" },
{ "code": 252, "label": "ü" }
],
".~normal": [
{ "code": 44, "label": "," },
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
],
".~uri": [
{ "code": -255, "label": ".com" },
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
"type": "characters/extended_popups",
"name": "it",
"authors": [ "patrickgold" ],
"mapping": {
"all": {
"a": {
"relevant": [
{ "code": 224, "label": "à" },
{ "code": 227, "label": "ã" },
{ "code": 229, "label": "å" },
{ "code": 257, "label": "ā" },
{ "code": 170, "label": "ª" },
{ "code": 225, "label": "á" },
{ "code": 226, "label": "â" },
{ "code": 228, "label": "ä" },
{ "code": 230, "label": "æ" }
]
},
"e": {
"relevant": [
{ "code": 232, "label": "è" },
{ "code": 275, "label": "ē" },
{ "code": 281, "label": "ę" },
{ "code": 279, "label": "ė" },
{ "code": 234, "label": "ê" },
{ "code": 233, "label": "é" },
{ "code": 235, "label": "ë" }
]
},
"i": {
"relevant": [
{ "code": 236, "label": "ì" },
{ "code": 299, "label": "ī" },
{ "code": 239, "label": "ï" },
{ "code": 303, "label": "į" },
{ "code": 238, "label": "î" },
{ "code": 237, "label": "í" }
]
},
"n": {
"relevant": [
{ "code": 241, "label": "ñ" },
{ "code": 324, "label": "ń" }
]
},
"o": {
"relevant": [
{ "code": 242, "label": "ò" },
{ "code": 186, "label": "º" },
{ "code": 333, "label": "ō" },
{ "code": 339, "label": "œ" },
{ "code": 248, "label": "ø" },
{ "code": 245, "label": "õ" },
{ "code": 246, "label": "ö" },
{ "code": 244, "label": "ô" },
{ "code": 243, "label": "ó" }
]
},
"u": {
"relevant": [
{ "code": 249, "label": "ù" },
{ "code": 363, "label": "ū" },
{ "code": 251, "label": "û" },
{ "code": 250, "label": "ú" },
{ "code": 252, "label": "ü" }
]
},
"~right": {
"main": { "code": 44, "label": "," },
"relevant": [
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".com" },
"relevant": [
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".it" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
}
}
}
}

View File

@@ -1,72 +1,99 @@
{
"a": [
{ "code": 229, "label": "å" },
{ "code": 225, "label": " },
{ "code": 226, "label": "â" },
{ "code": 227, "label": "ã" },
{ "code": 257, "label": "ā" },
{ "code": 230, "label": "æ" },
{ "code": 228, "label": "ä" },
{ "code": 224, "label": "à" }
],
"c": [
{ "code": 231, "label": "ç" }
],
"e": [
{ "code": 233, "label": "é" },
{ "code": 275, "label": "ē" },
{ "code": 281, "label": "ę" },
{ "code": 279, "label": "ė" },
{ "code": 234, "label": "ê" },
{ "code": 232, "label": "è" },
{ "code": 235, "label": "ë" }
],
"o": [
{ "code": 248, "label": "ø" },
{ "code": 333, "label": "ō" },
{ "code": 339, "label": "œ" },
{ "code": 243, "label": "ó" },
{ "code": 245, "label": "õ" },
{ "code": 242, "label": "ò" },
{ "code": 244, "label": "ô" },
{ "code": 246, "label": "ö" }
],
"u": [
{ "code": 252, "label": "ü" },
{ "code": 363, "label": "ū" },
{ "code": 249, "label": "ù" },
{ "code": 251, "label": "û" },
{ "code": 250, "label": "ú" }
],
"æ": [
{ "code": 228, "label": "ä" }
],
"ø": [
{ "code": 246, "label": "ö" }
],
".~normal": [
{ "code": 44, "label": "," },
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
],
".~uri": [
{ "code": -255, "label": ".com" },
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
"type": "characters/extended_popups",
"name": "nb",
"authors": [ "patrickgold" ],
"mapping": {
"all": {
"a": {
"relevant": [
{ "code": 229, "label": "å" },
{ "code": 225, "label": "á" },
{ "code": 226, "label": "â" },
{ "code": 227, "label": "ã" },
{ "code": 257, "label": "ā" },
{ "code": 230, "label": "æ" },
{ "code": 228, "label": "ä" },
{ "code": 224, "label": "à" }
]
},
"c": {
"relevant": [
{ "code": 231, "label": "ç" }
]
},
"e": {
"relevant": [
{ "code": 233, "label": "é" },
{ "code": 275, "label": "ē" },
{ "code": 281, "label": "ę" },
{ "code": 279, "label": "ė" },
{ "code": 234, "label": "ê" },
{ "code": 232, "label": "è" },
{ "code": 235, "label": "ë" }
]
},
"o": {
"relevant": [
{ "code": 248, "label": "ø" },
{ "code": 333, "label": "ō" },
{ "code": 339, "label": "œ" },
{ "code": 243, "label": "ó" },
{ "code": 245, "label": "õ" },
{ "code": 242, "label": "ò" },
{ "code": 244, "label": "ô" },
{ "code": 246, "label": "ö" }
]
},
"u": {
"relevant": [
{ "code": 252, "label": "ü" },
{ "code": 363, "label": "ū" },
{ "code": 249, "label": "ù" },
{ "code": 251, "label": "û" },
{ "code": 250, "label": "ú" }
]
},
"æ": {
"relevant": [
{ "code": 228, "label": "ä" }
]
},
"ø": {
"relevant": [
{ "code": 246, "label": "ö" }
]
},
"~right": {
"main": { "code": 44, "label": "," },
"relevant": [
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".com" },
"relevant": [
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
}
}
}
}

View File

@@ -1,78 +1,109 @@
{
"a": [
{ "code": 229, "label": "å" },
{ "code": 225, "label": " },
{ "code": 226, "label": "â" },
{ "code": 227, "label": "ã" },
{ "code": 257, "label": "ā" },
{ "code": 230, "label": "æ" },
{ "code": 228, "label": "ä" },
{ "code": 224, "label": "à" }
],
"c": [
{ "code": 231, "label": "ç" }
],
"e": [
{ "code": 233, "label": "é" },
{ "code": 275, "label": "ē" },
{ "code": 281, "label": "ę" },
{ "code": 279, "label": "ė" },
{ "code": 234, "label": "ê" },
{ "code": 232, "label": "è" },
{ "code": 235, "label": "ë" }
],
"i": [
{ "code": 236, "label": "ì" }
],
"o": [
{ "code": 248, "label": "ø" },
{ "code": 333, "label": "ō" },
{ "code": 339, "label": "œ" },
{ "code": 243, "label": "ó" },
{ "code": 245, "label": "õ" },
{ "code": 242, "label": "ò" },
{ "code": 244, "label": "ô" },
{ "code": 246, "label": "ö" }
],
"u": [
{ "code": 252, "label": "ü" },
{ "code": 363, "label": "ū" },
{ "code": 249, "label": "ù" },
{ "code": 251, "label": "û" },
{ "code": 250, "label": "ú" }
],
"y": [
{ "code": 7923, "label": "" }
],
"æ": [
{ "code": 228, "label": "ä" }
],
"ø": [
{ "code": 246, "label": "ö" }
],
".~normal": [
{ "code": 44, "label": "," },
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
],
".~uri": [
{ "code": -255, "label": ".com" },
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
"type": "characters/extended_popups",
"name": "nn",
"authors": [ "patrickgold" ],
"mapping": {
"all": {
"a": {
"relevant": [
{ "code": 229, "label": "å" },
{ "code": 225, "label": "á" },
{ "code": 226, "label": "â" },
{ "code": 227, "label": "ã" },
{ "code": 257, "label": "ā" },
{ "code": 230, "label": "æ" },
{ "code": 228, "label": "ä" },
{ "code": 224, "label": "à" }
]
},
"c": {
"relevant": [
{ "code": 231, "label": "ç" }
]
},
"e": {
"relevant": [
{ "code": 233, "label": "é" },
{ "code": 275, "label": "ē" },
{ "code": 281, "label": "ę" },
{ "code": 279, "label": "ė" },
{ "code": 234, "label": "ê" },
{ "code": 232, "label": "è" },
{ "code": 235, "label": "ë" }
]
},
"i": {
"relevant": [
{ "code": 236, "label": "ì" }
]
},
"o": {
"relevant": [
{ "code": 248, "label": "ø" },
{ "code": 333, "label": "ō" },
{ "code": 339, "label": "œ" },
{ "code": 243, "label": "ó" },
{ "code": 245, "label": "õ" },
{ "code": 242, "label": "ò" },
{ "code": 244, "label": "ô" },
{ "code": 246, "label": "ö" }
]
},
"u": {
"relevant": [
{ "code": 252, "label": "ü" },
{ "code": 363, "label": "ū" },
{ "code": 249, "label": "ù" },
{ "code": 251, "label": "û" },
{ "code": 250, "label": "ú" }
]
},
"y": {
"relevant": [
{ "code": 7923, "label": "" }
]
},
"æ": {
"relevant": [
{ "code": 228, "label": "ä" }
]
},
": {
"relevant": [
{ "code": 246, "label": "ö" }
]
},
"~right": {
"main": { "code": 44, "label": "," },
"relevant": [
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".com" },
"relevant": [
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
}
}
}
}

View File

@@ -0,0 +1,88 @@
{
"type": "characters/extended_popups",
"name": "pl",
"authors": [ "Mikołaj Biel" ],
"mapping": {
"all": {
"a": {
"relevant": [
{ "code": 261, "label": "ą" },
{ "code": 224, "label": "à" }
]
},
"c": {
"relevant": [
{ "code": 263, "label": "ć" }
]
},
"e": {
"relevant": [
{ "code": 281, "label": "ę" }
]
},
"l": {
"relevant": [
{ "code": 322, "label": "ł" }
]
},
"n": {
"relevant": [
{ "code": 324, "label": "ń" }
]
},
"o": {
"relevant": [
{ "code": 243, "label": "ó" }
]
},
"s": {
"relevant": [
{ "code": 347, "label": "ś" }
]
},
"x": {
"relevant": [
{ "code": 378, "label": "ź" }
]
},
"z": {
"relevant": [
{ "code": 378, "label": "ź" },
{ "code": 380, "label": "ż" }
]
},
"~right": {
"main": { "code": 44, "label": "," },
"relevant": [
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".com" },
"relevant": [
{ "code": -255, "label": ".pl" },
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
}
}
}
}

View File

@@ -0,0 +1,109 @@
{
"type": "characters/extended_popups",
"name": "pt-BR",
"authors": [ "rickym7" ],
"mapping": {
"all": {
"a": {
"relevant": [
{ "code": 228, "label": "ä" },
{ "code": 229, "label": "å" },
{ "code": 230, "label": "æ" },
{ "code": 170, "label": "ª" },
{ "code": 225, "label": "á" },
{ "code": 227, "label": "ã" },
{ "code": 224, "label": "à" },
{ "code": 226, "label": "â" }
]
},
"c": {
"relevant": [
{ "code": 269, "label": "č" },
{ "code": 231, "label": "ç" },
{ "code": 263, "label": "ć" }
]
},
"e": {
"relevant": [
{ "code": 275, "label": "ē" },
{ "code": 281, "label": "ę" },
{ "code": 279, "label": "ė" },
{ "code": 235, "label": "ë" },
{ "code": 234, "label": "ê" },
{ "code": 233, "label": "é" },
{ "code": 232, "label": "è" }
]
},
"i": {
"relevant": [
{ "code": 299, "label": "ī" },
{ "code": 239, "label": "ï" },
{ "code": 303, "label": "į" },
{ "code": 236, "label": "ì" },
{ "code": 238, "label": "î" },
{ "code": 237, "label": "í" }
]
},
"n": {
"relevant": [
{ "code": 241, "label": "ñ" },
{ "code": 324, "label": "ń" }
]
},
"o": {
"relevant": [
{ "code": 186, "label": "º" },
{ "code": 333, "label": "ō" },
{ "code": 248, "label": "ø" },
{ "code": 339, "label": "œ" },
{ "code": 246, "label": "ö" },
{ "code": 242, "label": "ò" },
{ "code": 244, "label": "ô" },
{ "code": 245, "label": "õ" },
{ "code": 243, "label": "ó" }
]
},
"u": {
"relevant": [
{ "code": 363, "label": "ū" },
{ "code": 249, "label": "ù" },
{ "code": 251, "label": "û" },
{ "code": 252, "label": "ü" },
{ "code": 250, "label": "ú" }
]
},
"~right": {
"main": { "code": 44, "label": "," },
"relevant": [
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".com" },
"relevant": [
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".br" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
}
}
}
}

View File

@@ -1,81 +1,109 @@
{
"a": [
{ "code": 225, "label": "á" },
{ "code": 228, "label": " },
{ "code": 229, "label": "å" },
{ "code": 230, "label": "æ" },
{ "code": 170, "label": "ª" },
{ "code": 227, "label": "ã" },
{ "code": 224, "label": "à" },
{ "code": 226, "label": "â" }
],
"c": [
{ "code": 231, "label": "ç" },
{ "code": 263, "label": "ć" },
{ "code": 269, "label": "č" }
],
"e": [
{ "code": 233, "label": "é" },
{ "code": 235, "label": "ë" },
{ "code": 279, "label": "ė" },
{ "code": 275, "label": "ē" },
{ "code": 232, "label": "è" },
{ "code": 234, "label": "ê" },
{ "code": 281, "label": "ę" }
],
"i": [
{ "code": 237, "label": "í" },
{ "code": 299, "label": "ī" },
{ "code": 239, "label": "ï" },
{ "code": 303, "label": "į" },
{ "code": 236, "label": "ì" },
{ "code": 238, "label": "î" }
],
"n": [
{ "code": 241, "label": "ñ" },
{ "code": 324, "label": "ń" }
],
"o": [
{ "code": 243, "label": "ó" },
{ "code": 186, "label": "º" },
{ "code": 333, "label": "ō" },
{ "code": 248, "label": "ø" },
{ "code": 339, "label": "œ" },
{ "code": 246, "label": "ö" },
{ "code": 242, "label": "ò" },
{ "code": 244, "label": "ô" },
{ "code": 245, "label": "õ" }
],
"u": [
{ "code": 250, "label": "ú" },
{ "code": 363, "label": "ū" },
{ "code": 249, "label": "ù" },
{ "code": 252, "label": "ü" },
{ "code": 251, "label": "û" }
],
".~normal": [
{ "code": 44, "label": "," },
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
],
".~uri": [
{ "code": -255, "label": ".com" },
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
"type": "characters/extended_popups",
"name": "pt",
"authors": [ "patrickgold" ],
"mapping": {
"all": {
"a": {
"relevant": [
{ "code": 228, "label": "ä" },
{ "code": 229, "label": "å" },
{ "code": 230, "label": "æ" },
{ "code": 170, "label": "ª" },
{ "code": 225, "label": "á" },
{ "code": 227, "label": "ã" },
{ "code": 224, "label": "à" },
{ "code": 226, "label": "â" }
]
},
"c": {
"relevant": [
{ "code": 231, "label": "ç" },
{ "code": 263, "label": "ć" },
{ "code": 269, "label": "č" }
]
},
"e": {
"relevant": [
{ "code": 235, "label": "ë" },
{ "code": 279, "label": "ė" },
{ "code": 275, "label": "ē" },
{ "code": 233, "label": "é" },
{ "code": 232, "label": "è" },
{ "code": 234, "label": "ê" },
{ "code": 281, "label": "ę" }
]
},
"i": {
"relevant": [
{ "code": 299, "label": "ī" },
{ "code": 239, "label": "ï" },
{ "code": 303, "label": "į" },
{ "code": 236, "label": "ì" },
{ "code": 237, "label": "í" },
{ "code": 238, "label": "î" }
]
},
"n": {
"relevant": [
{ "code": 241, "label": "ñ" },
{ "code": 324, "label": "ń" }
]
},
"o": {
"relevant": [
{ "code": 186, "label": "º" },
{ "code": 333, "label": "ō" },
{ "code": 248, "label": "ø" },
{ "code": 339, "label": "œ" },
{ "code": 246, "label": "ö" },
{ "code": 242, "label": "ò" },
{ "code": 244, "label": "ô" },
{ "code": 243, "label": "ó" },
{ "code": 245, "label": "õ" }
]
},
"u": {
"relevant": [
{ "code": 363, "label": "ū" },
{ "code": 249, "label": "ù" },
{ "code": 250, "label": "ú" },
{ "code": 252, "label": "ü" },
{ "code": 251, "label": "û" }
]
},
"~right": {
"main": { "code": 44, "label": "," },
"relevant": [
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".com" },
"relevant": [
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".pt" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
}
}
}
}

View File

@@ -0,0 +1,55 @@
{
"type": "characters/extended_popups",
"name": "ro",
"authors": [ "bertin0" ],
"mapping": {
"all": {
"a": {
"main": { "code": 259, "label": "ă" },
"relevant": [
{ "code": 226, "label": "â" }
]
},
"i": {
"main": { "code": 238, "label": "î" }
},
"s": {
"main": {"code": 537, "label": "ș"}
},
"t": {
"main": {"code": 539, "label": "ț"}
},
"~right": {
"main": { "code": 44, "label": "," },
"relevant": [
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".com" },
"relevant": [
{ "code": -255, "label": ".ro" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" },
{ "code": -255, "label": ".edu" }
]
}
}
}
}

View File

@@ -0,0 +1,45 @@
{
"type": "characters/extended_popups",
"name": "ru",
"authors": [ "williamtheaker" ],
"mapping": {
"all": {
"е": {
"relevant": [
{ "code": 1105, "label": "ё" }
]
},
"~right": {
"main": { "code": 44, "label": "," },
"relevant": [
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".com" },
"relevant": [
{ "code": -255, "label": ".ru" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
}
}
}
}

View File

@@ -1,115 +1,160 @@
{
"a": [
{ "code": 228, "label": "ä" },
{ "code": 224, "label": " },
{ "code": 226, "label": "â" },
{ "code": 261, "label": "ą" },
{ "code": 227, "label": "ã" },
{ "code": 229, "label": "å" },
{ "code": 230, "label": "æ" },
{ "code": 225, "label": "á" }
],
"c": [
{ "code": 231, "label": "ç" },
{ "code": 269, "label": "č" },
{ "code": 263, "label": "ć" }
],
"d": [
{ "code": 240, "label": "ð" },
{ "code": 271, "label": "ď" }
],
"e": [
{ "code": 233, "label": "é" },
{ "code": 234, "label": "ê" },
{ "code": 232, "label": "è" },
{ "code": 235, "label": "ë" },
{ "code": 281, "label": "ę" }
],
"i": [
{ "code": 237, "label": "í" },
{ "code": 239, "label": "ï" },
{ "code": 299, "label": "ī" },
{ "code": 303, "label": "į" },
{ "code": 238, "label": "î" },
{ "code": 236, "label": "ì" }
],
"l": [
{ "code": 322, "label": "ł" }
],
"n": [
{ "code": 324, "label": "ń" },
{ "code": 328, "label": "ň" },
{ "code": 241, "label": "ñ" }
],
"o": [
{ "code": 246, "label": "ö" },
{ "code": 333, "label": "ō" },
{ "code": 245, "label": "õ" },
{ "code": 242, "label": "ò" },
{ "code": 244, "label": "ô" },
{ "code": 243, "label": "ó" },
{ "code": 339, "label": "œ" },
{ "code": 248, "label": "ø" }
],
"r": [
{ "code": 345, "label": "ř" }
],
"s": [
{ "code": 347, "label": "ś" },
{ "code": 353, "label": "š" },
{ "code": 351, "label": "ş" },
{ "code": 223, "label": "ß" }
],
"t": [
{ "code": 357, "label": "ť" },
{ "code": 254, "label": "þ" }
],
"u": [
{ "code": 252, "label": "ü" },
{ "code": 363, "label": "ū" },
{ "code": 249, "label": "ù" },
{ "code": 250, "label": "ú" },
{ "code": 251, "label": "û" }
],
"y": [
{ "code": 253, "label": "ý" },
{ "code": 255, "label": "ÿ" }
],
"z": [
{ "code": 378, "label": "ź" },
{ "code": 380, "label": "ż" },
{ "code": 382, "label": "ž" }
],
"ä": [
{ "code": 230, "label": "æ" }
],
"ö": [
{ "code": 248, "label": "ø" },
{ "code": 339, "label": "œ" }
],
".~normal": [
{ "code": 44, "label": "," },
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
],
".~uri": [
{ "code": -255, "label": ".com" },
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
"type": "characters/extended_popups",
"name": "sv",
"authors": [ "patrickgold" ],
"mapping": {
"all": {
"a": {
"relevant": [
{ "code": 228, "label": "ä" },
{ "code": 224, "label": "à" },
{ "code": 226, "label": "â" },
{ "code": 261, "label": "ą" },
{ "code": 227, "label": "ã" },
{ "code": 229, "label": "å" },
{ "code": 230, "label": "æ" },
{ "code": 225, "label": "á" }
]
},
"c": {
"relevant": [
{ "code": 231, "label": "ç" },
{ "code": 269, "label": "č" },
{ "code": 263, "label": "ć" }
]
},
"d": {
"relevant": [
{ "code": 240, "label": "ð" },
{ "code": 271, "label": "ď" }
]
},
"e": {
"relevant": [
{ "code": 233, "label": "é" },
{ "code": 234, "label": "ê" },
{ "code": 232, "label": "è" },
{ "code": 235, "label": "ë" },
{ "code": 281, "label": "ę" }
]
},
"i": {
"relevant": [
{ "code": 237, "label": "í" },
{ "code": 239, "label": "ï" },
{ "code": 299, "label": "ī" },
{ "code": 303, "label": "į" },
{ "code": 238, "label": "î" },
{ "code": 236, "label": "ì" }
]
},
"l": {
"relevant": [
{ "code": 322, "label": "ł" }
]
},
"n": {
"relevant": [
{ "code": 324, "label": "ń" },
{ "code": 328, "label": "ň" },
{ "code": 241, "label": "ñ" }
]
},
"o": {
"relevant": [
{ "code": 246, "label": "ö" },
{ "code": 333, "label": "ō" },
{ "code": 245, "label": "õ" },
{ "code": 242, "label": "ò" },
{ "code": 244, "label": "ô" },
{ "code": 243, "label": "ó" },
{ "code": 339, "label": "œ" },
{ "code": 248, "label": "ø" }
]
},
"r": {
"relevant": [
{ "code": 345, "label": "ř" }
]
},
"s": {
"relevant": [
{ "code": 347, "label": "ś" },
{ "code": 353, "label": "š" },
{ "code": 351, "label": "ş" },
{ "code": 223, "label": "ß" }
]
},
"t": {
"relevant": [
{ "code": 357, "label": "ť" },
{ "code": 254, "label": "þ" }
]
},
"u": {
"relevant": [
{ "code": 252, "label": "ü" },
{ "code": 363, "label": "ū" },
{ "code": 249, "label": "ù" },
{ "code": 250, "label": "ú" },
{ "code": 251, "label": "û" }
]
},
"y": {
"relevant": [
{ "code": 253, "label": "ý" },
{ "code": 255, "label": "ÿ" }
]
},
"z": {
"relevant": [
{ "code": 378, "label": "ź" },
{ "code": 380, "label": "ż" },
{ "code": 382, "label": "ž" }
]
},
"ä": {
"relevant": [
{ "code": 230, "label": "æ" }
]
},
"ö": {
"relevant": [
{ "code": 248, "label": "ø" },
{ "code": 339, "label": "œ" }
]
},
"~right": {
"main": { "code": 44, "label": "," },
"relevant": [
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".com" },
"relevant": [
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
}
}
}
}

View File

@@ -0,0 +1,40 @@
{
"type": "characters",
"name": "greek",
"authors": [ "tsiflimagas" ],
"direction": "ltr",
"arrangement": [
[
{ "code": 894, "label": ";" },
{ "code": 962, "label": "ς" },
{ "code": 949, "label": "ε" },
{ "code": 961, "label": "ρ" },
{ "code": 964, "label": "τ" },
{ "code": 965, "label": "υ" },
{ "code": 952, "label": "θ" },
{ "code": 953, "label": "ι" },
{ "code": 959, "label": "ο" },
{ "code": 960, "label": "π" }
],
[
{ "code": 945, "label": "α" },
{ "code": 963, "label": "σ" },
{ "code": 948, "label": "δ" },
{ "code": 966, "label": "φ" },
{ "code": 947, "label": "γ" },
{ "code": 951, "label": "η" },
{ "code": 958, "label": "ξ" },
{ "code": 954, "label": "κ" },
{ "code": 955, "label": "λ" }
],
[
{ "code": 950, "label": "ζ" },
{ "code": 967, "label": "χ" },
{ "code": 968, "label": "ψ" },
{ "code": 969, "label": "ω" },
{ "code": 946, "label": "β" },
{ "code": 957, "label": "ν" },
{ "code": 956, "label": "μ" }
]
]
}

View File

@@ -0,0 +1,44 @@
{
"type": "characters",
"name": "hungarian",
"authors": [ "zoli111" ],
"direction": "ltr",
"arrangement": [
[
{ "code": 113, "label": "q" },
{ "code": 119, "label": "w" },
{ "code": 101, "label": "e" },
{ "code": 114, "label": "r" },
{ "code": 116, "label": "t" },
{ "code": 122, "label": "z" },
{ "code": 117, "label": "u" },
{ "code": 105, "label": "i" },
{ "code": 111, "label": "o" },
{ "code": 112, "label": "p" },
{ "code": 246, "label": "ö" }
],
[
{ "code": 97, "label": "a" },
{ "code": 115, "label": "s" },
{ "code": 100, "label": "d" },
{ "code": 102, "label": "f" },
{ "code": 103, "label": "g" },
{ "code": 104, "label": "h" },
{ "code": 106, "label": "j" },
{ "code": 107, "label": "k" },
{ "code": 108, "label": "l" },
{ "code": 233, "label": "é" },
{ "code": 225, "label": "á" }
],
[
{ "code": 121, "label": "y" },
{ "code": 120, "label": "x" },
{ "code": 99, "label": "c" },
{ "code": 118, "label": "v" },
{ "code": 98, "label": "b" },
{ "code": 110, "label": "n" },
{ "code": 109, "label": "m" },
{ "code": 252, "label": "ü" }
]
]
}

View File

@@ -1,6 +1,7 @@
{
"type": "characters",
"name": "icelandic",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
@@ -15,7 +16,8 @@
{ "code": 111, "label": "o" },
{ "code": 112, "label": "p" },
{ "code": 240, "label": "ð" }
], [
],
[
{ "code": 97, "label": "a" },
{ "code": 115, "label": "s" },
{ "code": 100, "label": "d" },
@@ -27,7 +29,8 @@
{ "code": 108, "label": "l" },
{ "code": 230, "label": "æ" },
{ "code": 246, "label": "ö" }
], [
],
[
{ "code": 122, "label": "z" },
{ "code": 120, "label": "x" },
{ "code": 99, "label": "c" },

View File

@@ -0,0 +1,46 @@
{
"type": "characters",
"name": "jcuken_russian",
"authors": [ "williamtheaker" ],
"direction": "ltr",
"arrangement": [
[
{ "code": 1081, "label": "й" },
{ "code": 1094, "label": "ц" },
{ "code": 1091, "label": "у" },
{ "code": 1082, "label": "к" },
{ "code": 1077, "label": "е" },
{ "code": 1085, "label": "н" },
{ "code": 1075, "label": "г" },
{ "code": 1096, "label": "ш" },
{ "code": 1097, "label": "щ" },
{ "code": 1079, "label": "з" },
{ "code": 1093, "label": "х" },
{ "code": 1098, "label": "ъ" }
],
[
{ "code": 1092 , "label": "ф" },
{ "code": 1099 , "label": "ы" },
{ "code": 1074 , "label": "в" },
{ "code": 1072 , "label": "а" },
{ "code": 1087 , "label": "п" },
{ "code": 1088 , "label": "р" },
{ "code": 1086 , "label": "о" },
{ "code": 1083 , "label": "л" },
{ "code": 1076 , "label": "д" },
{ "code": 1078 , "label": "ж" },
{ "code": 1101 , "label": "э" }
],
[
{ "code": 1103 , "label": "я" },
{ "code": 1095 , "label": "ч" },
{ "code": 1089 , "label": "с" },
{ "code": 1084 , "label": "м" },
{ "code": 1080 , "label": "и" },
{ "code": 1090 , "label": "т" },
{ "code": 1100 , "label": "ь" },
{ "code": 1073 , "label": "б" },
{ "code": 1102 , "label": "ю" }
]
]
}

View File

@@ -0,0 +1,27 @@
{
"type": "characters/mod",
"name": "arabic",
"authors": [ "HeiWiper" ],
"direction": "rtl",
"arrangement": [
[
{ "code": 0, "type": "placeholder" },
{ "code": -5, "label": "delete", "type": "enter_editing" }
],
[
{ "code": -202, "label": "view_symbols", "type": "system_gui" },
{ "code": 64, "label": "@", "groupId": 1, "variation": "email_address" },
{ "code": 1548, "label": "،", "groupId": 1, "variation": "normal" },
{ "code": 1548, "label": "،", "groupId": 1, "variation": "password" },
{ "code": 47, "label": "/", "groupId": 1, "variation": "uri" },
{ "code": -210, "label": "language_switch", "type": "system_gui" },
{ "code": -213, "label": "switch_to_media_context", "type": "system_gui" },
{ "code": 32, "label": "space" },
{ "code": 46, "label": ".", "groupId": 2, "variation": "email_address" },
{ "code": 46, "label": ".", "groupId": 2, "variation": "normal" },
{ "code": 46, "label": ".", "groupId": 2, "variation": "password" },
{ "code": 46, "label": ".", "groupId": 2, "variation": "uri" },
{ "code": 10, "label": "enter", "groupId": 3, "type": "enter_editing" }
]
]
}

View File

@@ -1,32 +1,28 @@
{
"type": "characters/mod",
"name": "default",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
{ "code": -1, "label": "shift", "type": "modifier" },
{ "code": 0 },
{ "code": 0, "type": "placeholder" },
{ "code": -5, "label": "delete", "type": "enter_editing" }
], [
],
[
{ "code": -202, "label": "view_symbols", "type": "system_gui" },
{ "code": 64, "label": "@", "variation": "email_address" },
{ "code": 44, "label": ",", "variation": "normal" },
{ "code": 47, "label": "/", "variation": "uri" },
{ "code": -210, "label": "language_switch", "type": "system_gui", "popup": [
{ "code": -213, "label": "switch_to_media_context", "type": "system_gui" },
{ "code": -100, "label": "settings", "type": "system_gui" }
] },
{ "code": -213, "label": "switch_to_media_context", "type": "system_gui", "popup": [
{ "code": -100, "label": "settings", "type": "system_gui" }
] },
{ "code": 32, "label": " " },
{ "code": 46, "label": ".", "variation": "email_address" },
{ "code": 46, "label": ".", "variation": "normal" },
{ "code": 46, "label": ".", "variation": "uri" },
{ "code": 10, "label": "enter", "type": "enter_editing", "popup": [
{ "code": -215, "label": "toggle_one_handed_mode", "type": "system_gui" },
{ "code": -213, "label": "switch_to_media_context", "type": "system_gui" }
] }
{ "code": 64, "label": "@", "groupId": 1, "variation": "email_address" },
{ "code": 44, "label": ",", "groupId": 1, "variation": "normal" },
{ "code": 44, "label": ",", "groupId": 1, "variation": "password" },
{ "code": 47, "label": "/", "groupId": 1, "variation": "uri" },
{ "code": -210, "label": "language_switch", "type": "system_gui" },
{ "code": -213, "label": "switch_to_media_context", "type": "system_gui" },
{ "code": 32, "label": "space" },
{ "code": 46, "label": ".", "groupId": 2, "variation": "email_address" },
{ "code": 46, "label": ".", "groupId": 2, "variation": "normal" },
{ "code": 46, "label": ".", "groupId": 2, "variation": "password" },
{ "code": 46, "label": ".", "groupId": 2, "variation": "uri" },
{ "code": 10, "label": "enter", "groupId": 3, "type": "enter_editing" }
]
]
}

View File

@@ -0,0 +1,25 @@
{
"type": "characters/mod",
"name": "dvorak",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
{ "code": -1, "label": "shift", "type": "modifier" },
{ "code": 0, "type": "placeholder" },
{ "code": -5, "label": "delete", "type": "enter_editing" }
],
[
{ "code": -202, "label": "view_symbols", "type": "system_gui" },
{ "code": 113, "label": "q", "groupId": 1 },
{ "code": -210, "label": "language_switch", "type": "system_gui" },
{ "code": -213, "label": "switch_to_media_context", "type": "system_gui" },
{ "code": 32, "label": "space" },
{ "code": 122, "label": "z", "groupId": 2, "variation": "email_address" },
{ "code": 122, "label": "z", "groupId": 2, "variation": "normal" },
{ "code": 122, "label": "z", "groupId": 2, "variation": "password" },
{ "code": 122, "label": "z", "groupId": 2, "variation": "uri" },
{ "code": 10, "label": "enter", "groupId": 3, "type": "enter_editing" }
]
]
}

View File

@@ -1,34 +1,31 @@
{
"type": "characters/mod",
"name": "persian",
"authors": [ "PHELAT" ],
"direction": "rtl",
"arrangement": [
[
{ "code": 0 },
{ "code": 0, "type": "placeholder" },
{ "code": -5, "label": "delete", "type": "enter_editing" }
],
[
{ "code": -202, "label": "view_symbols", "type": "system_gui" },
{ "code": 64, "label": "@", "variation": "email_address" },
{ "code": 1548, "label": "،", "variation": "normal" },
{ "code": 47, "label": "/", "variation": "uri" },
{ "code": -210, "label": "language_switch", "type": "system_gui", "popup": [
{ "code": -213, "label": "switch_to_media_context", "type": "system_gui" },
{ "code": -100, "label": "settings", "type": "system_gui" }
] },
{ "code": -213, "label": "switch_to_media_context", "type": "system_gui", "popup": [
{ "code": -100, "label": "settings", "type": "system_gui" }
] },
{ "code": 32, "label": " " },
{ "code": 64, "label": "@", "groupId": 1, "variation": "email_address" },
{ "code": 1548, "label": "،", "groupId": 1, "variation": "normal" },
{ "code": 1548, "label": "،", "groupId": 1, "variation": "password" },
{ "code": 47, "label": "/", "groupId": 1, "variation": "uri" },
{ "code": -210, "label": "language_switch", "type": "system_gui" },
{ "code": -213, "label": "switch_to_media_context", "type": "system_gui" },
{ "code": 32, "label": "space" },
{ "code": 8204, "label": "half_space", "variation": "normal" },
{ "code": 8204, "label": "half_space", "variation": "password" },
{ "code": 1600, "label": "kashida", "variation": "normal" },
{ "code": 46, "label": ".", "variation": "email_address" },
{ "code": 46, "label": ".", "variation": "normal" },
{ "code": 46, "label": ".", "variation": "uri" },
{ "code": 10, "label": "enter", "type": "enter_editing", "popup": [
{ "code": -215, "label": "toggle_one_handed_mode", "type": "system_gui" },
{ "code": -213, "label": "switch_to_media_context", "type": "system_gui" }
] }
{ "code": 1600, "label": "kashida", "variation": "password" },
{ "code": 46, "label": ".", "groupId": 2, "variation": "email_address" },
{ "code": 46, "label": ".", "groupId": 2, "variation": "normal" },
{ "code": 46, "label": ".", "groupId": 2, "variation": "password" },
{ "code": 46, "label": ".", "groupId": 2, "variation": "uri" },
{ "code": 10, "label": "enter", "groupId": 3, "type": "enter_editing" }
]
]
}

View File

@@ -1,6 +1,7 @@
{
"type": "characters",
"name": "norwegian",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
@@ -15,7 +16,8 @@
{ "code": 111, "label": "o" },
{ "code": 112, "label": "p" },
{ "code": 229, "label": "å" }
], [
],
[
{ "code": 97, "label": "a" },
{ "code": 115, "label": "s" },
{ "code": 100, "label": "d" },
@@ -27,7 +29,8 @@
{ "code": 108, "label": "l" },
{ "code": 248, "label": "ø" },
{ "code": 230, "label": "æ" }
], [
],
[
{ "code": 122, "label": "z" },
{ "code": 120, "label": "x" },
{ "code": 99, "label": "c" },

View File

@@ -1,6 +1,7 @@
{
"type": "characters",
"name": "persian",
"authors": [ "PHELAT" ],
"direction": "rtl",
"modifier": "persian",
"arrangement": [

View File

@@ -1,6 +1,7 @@
{
"type": "characters",
"name": "qwerty",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
@@ -14,7 +15,8 @@
{ "code": 105, "label": "i" },
{ "code": 111, "label": "o" },
{ "code": 112, "label": "p" }
], [
],
[
{ "code": 97, "label": "a" },
{ "code": 115, "label": "s" },
{ "code": 100, "label": "d" },
@@ -24,7 +26,8 @@
{ "code": 106, "label": "j" },
{ "code": 107, "label": "k" },
{ "code": 108, "label": "l" }
], [
],
[
{ "code": 122, "label": "z" },
{ "code": 120, "label": "x" },
{ "code": 99, "label": "c" },

View File

@@ -1,6 +1,7 @@
{
"type": "characters",
"name": "qwertz",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
@@ -14,7 +15,8 @@
{ "code": 105, "label": "i" },
{ "code": 111, "label": "o" },
{ "code": 112, "label": "p" }
], [
],
[
{ "code": 97, "label": "a" },
{ "code": 115, "label": "s" },
{ "code": 100, "label": "d" },
@@ -24,7 +26,8 @@
{ "code": 106, "label": "j" },
{ "code": 107, "label": "k" },
{ "code": 108, "label": "l" }
], [
],
[
{ "code": 121, "label": "y" },
{ "code": 120, "label": "x" },
{ "code": 99, "label": "c" },

View File

@@ -1,6 +1,7 @@
{
"type": "characters",
"name": "spanish",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
@@ -14,7 +15,8 @@
{ "code": 105, "label": "i" },
{ "code": 111, "label": "o" },
{ "code": 112, "label": "p" }
], [
],
[
{ "code": 97, "label": "a" },
{ "code": 115, "label": "s" },
{ "code": 100, "label": "d" },
@@ -25,7 +27,8 @@
{ "code": 107, "label": "k" },
{ "code": 108, "label": "l" },
{ "code": 241, "label": "ñ" }
], [
],
[
{ "code": 122, "label": "z" },
{ "code": 120, "label": "x" },
{ "code": 99, "label": "c" },

View File

@@ -1,6 +1,7 @@
{
"type": "characters",
"name": "swedish_finnish",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
@@ -15,7 +16,8 @@
{ "code": 111, "label": "o" },
{ "code": 112, "label": "p" },
{ "code": 229, "label": "å" }
], [
],
[
{ "code": 97, "label": "a" },
{ "code": 115, "label": "s" },
{ "code": 100, "label": "d" },
@@ -27,7 +29,8 @@
{ "code": 108, "label": "l" },
{ "code": 246, "label": "ö" },
{ "code": 228, "label": "ä" }
], [
],
[
{ "code": 122, "label": "z" },
{ "code": 120, "label": "x" },
{ "code": 99, "label": "c" },

View File

@@ -1,6 +1,7 @@
{
"type": "characters",
"name": "swiss_french",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
@@ -14,10 +15,13 @@
{ "code": 105, "label": "i" },
{ "code": 111, "label": "o" },
{ "code": 112, "label": "p" },
{ "code": 232, "label": "è", "popup": [
{ "code": 252, "label": "ü" }
] }
], [
{ "code": 232, "label": "è", "popup": {
"relevant": [
{ "code": 252, "label": "ü" }
]
} }
],
[
{ "code": 97, "label": "a" },
{ "code": 115, "label": "s" },
{ "code": 100, "label": "d" },
@@ -27,13 +31,18 @@
{ "code": 106, "label": "j" },
{ "code": 107, "label": "k" },
{ "code": 108, "label": "l" },
{ "code": 233, "label": "é", "popup": [
{ "code": 246, "label": "ö" }
] },
{ "code": 224, "label": "à", "popup": [
{ "code": 228, "label": "ä" }
] }
], [
{ "code": 233, "label": "é", "popup": {
"relevant": [
{ "code": 246, "label": "ö" }
]
} },
{ "code": 224, "label": "à", "popup": {
"relevant": [
{ "code": 228, "label": "ä" }
]
} }
],
[
{ "code": 121, "label": "y" },
{ "code": 120, "label": "x" },
{ "code": 99, "label": "c" },

View File

@@ -1,6 +1,7 @@
{
"type": "characters",
"name": "swiss_german",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
@@ -14,10 +15,13 @@
{ "code": 105, "label": "i" },
{ "code": 111, "label": "o" },
{ "code": 112, "label": "p" },
{ "code": 252, "label": "ü", "popup": [
{ "code": 232, "label": "è" }
] }
], [
{ "code": 252, "label": "ü", "popup": {
"relevant": [
{ "code": 232, "label": "è" }
]
} }
],
[
{ "code": 97, "label": "a" },
{ "code": 115, "label": "s" },
{ "code": 100, "label": "d" },
@@ -27,13 +31,18 @@
{ "code": 106, "label": "j" },
{ "code": 107, "label": "k" },
{ "code": 108, "label": "l" },
{ "code": 246, "label": "ö", "popup": [
{ "code": 233, "label": "é" }
] },
{ "code": 228, "label": "ä", "popup": [
{ "code": 224, "label": "à" }
] }
], [
{ "code": 246, "label": "ö", "popup": {
"relevant": [
{ "code": 233, "label": "é" }
]
} },
{ "code": 228, "label": "ä", "popup": {
"relevant": [
{ "code": 224, "label": "à" }
]
} }
],
[
{ "code": 121, "label": "y" },
{ "code": 120, "label": "x" },
{ "code": 99, "label": "c" },

View File

@@ -1,6 +1,7 @@
{
"type": "characters",
"name": "swiss_italian",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
@@ -14,10 +15,13 @@
{ "code": 105, "label": "i" },
{ "code": 111, "label": "o" },
{ "code": 112, "label": "p" },
{ "code": 252, "label": "ü", "popup": [
{ "code": 232, "label": "è" }
] }
], [
{ "code": 252, "label": "ü", "popup": {
"relevant": [
{ "code": 232, "label": "è" }
]
} }
],
[
{ "code": 97, "label": "a" },
{ "code": 115, "label": "s" },
{ "code": 100, "label": "d" },
@@ -27,13 +31,18 @@
{ "code": 106, "label": "j" },
{ "code": 107, "label": "k" },
{ "code": 108, "label": "l" },
{ "code": 246, "label": "ö", "popup": [
{ "code": 233, "label": "é" }
] },
{ "code": 228, "label": "ä", "popup": [
{ "code": 224, "label": "à" }
] }
], [
{ "code": 246, "label": "ö", "popup": {
"relevant": [
{ "code": 233, "label": "é" }
]
} },
{ "code": 228, "label": "ä", "popup": {
"relevant": [
{ "code": 224, "label": "à" }
]
} }
],
[
{ "code": 121, "label": "y" },
{ "code": 120, "label": "x" },
{ "code": 99, "label": "c" },

View File

@@ -0,0 +1,16 @@
{
"type": "extension",
"name": "clipboard_cursor_row",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
{ "code": -135, "label": "clipboard_select_all", "type": "enter_editing" },
{ "code": -130, "label": "clipboard_copy", "type": "enter_editing" },
{ "code": -20, "label": "arrow_left", "type": "navigation" },
{ "code": -21, "label": "arrow_right", "type": "navigation" },
{ "code": -131, "label": "clipboard_cut", "type": "enter_editing" },
{ "code": -132, "label": "clipboard_paste", "type": "enter_editing" }
]
]
}

View File

@@ -1,41 +1,74 @@
{
"type": "extension",
"name": "number_row",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
{ "code": 49, "label": "1", "popup": [
{ "code": 185, "label": "¹" },
{ "code": 189, "label": "½" },
{ "code": 8531, "label": "" },
{ "code": 188, "label": "¼" },
{ "code": 8539, "label": "⅛" }
] },
{ "code": 50, "label": "2", "popup": [
{ "code": 178, "label": "²" },
{ "code": 8532, "label": "" }
] },
{ "code": 51, "label": "3", "popup": [
{ "code": 8540, "label": "⅜" },
{ "code": 179, "label": "³" },
{ "code": 190, "label": "¾" }
] },
{ "code": 52, "label": "4", "popup": [
{ "code": 8308, "label": "" }
] },
{ "code": 53, "label": "5", "popup": [
{ "code": 8541, "label": "⅝" }
] },
{ "code": 54, "label": "6", "popup": [] },
{ "code": 55, "label": "7", "popup": [
{ "code": 8542, "label": "" }
] },
{ "code": 56, "label": "8", "popup": [] },
{ "code": 57, "label": "9", "popup": [] },
{ "code": 48, "label": "0", "popup": [
{ "code": 8709, "label": "" },
{ "code": 8319, "label": "" }
] }
{ "code": 49, "label": "1", "type": "numeric", "popup": {
"main": { "code": 185, "label": "¹" },
"relevant": [
{ "code": 8537, "label": "" },
{ "code": 8528, "label": "" },
{ "code": 8539, "label": "⅛" },
{ "code": 8529, "label": "⅑" },
{ "code": 8530, "label": "" },
{ "code": 189, "label": "½" },
{ "code": 8531, "label": "" },
{ "code": 188, "label": "¼" },
{ "code": 8533, "label": "⅕" }
]
} },
{ "code": 50, "label": "2", "type": "numeric", "popup": {
"main": { "code": 178, "label": "²" },
"relevant": [
{ "code": 8532, "label": "" },
{ "code": 8534, "label": "⅖" }
]
} },
{ "code": 51, "label": "3", "type": "numeric", "popup": {
"main": { "code": 179, "label": "³" },
"relevant": [
{ "code": 8535, "label": "" },
{ "code": 190, "label": "¾" },
{ "code": 8540, "label": "⅜" }
]
} },
{ "code": 52, "label": "4", "type": "numeric", "popup": {
"main": { "code": 8308, "label": "" },
"relevant": [
{ "code": 8536, "label": "⅘" }
]
} },
{ "code": 53, "label": "5", "type": "numeric", "popup": {
"main": { "code": 8309, "label": "⁵" },
"relevant": [
{ "code": 8538, "label": "⅚" },
{ "code": 8541, "label": "⅝" }
]
} },
{ "code": 54, "label": "6", "type": "numeric", "popup": {
"main": { "code": 8310, "label": "⁶" }
} },
{ "code": 55, "label": "7", "type": "numeric", "popup": {
"main": { "code": 8311, "label": "⁷" },
"relevant": [
{ "code": 8542, "label": "⅞" }
]
} },
{ "code": 56, "label": "8", "type": "numeric", "popup": {
"main": { "code": 8312, "label": "⁸" }
} },
{ "code": 57, "label": "9", "type": "numeric", "popup": {
"main": { "code": 8313, "label": "⁹" }
} },
{ "code": 48, "label": "0", "type": "numeric", "popup": {
"main": { "code": 8304, "label": "⁰" },
"relevant": [
{ "code": 8709, "label": "∅" },
{ "code": 8319, "label": "ⁿ" }
]
} }
]
]
}

View File

@@ -1,43 +1,53 @@
{
"type": "numeric_advanced",
"name": "default",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
{ "code": 43, "label": "+", "popup": [
{ "code": 45, "label": "-" },
{ "code": 42, "label": "*" },
{ "code": 47, "label": "/" }
] },
{ "code": 43, "label": "+", "popup": {
"relevant": [
{ "code": 45, "label": "-" },
{ "code": 42, "label": "*" },
{ "code": 47, "label": "/" }
]
} },
{ "code": 49, "label": "1", "type": "numeric" },
{ "code": 50, "label": "2", "type": "numeric" },
{ "code": 51, "label": "3", "type": "numeric" },
{ "code": 37, "label": "%", "popup": [] }
], [
{ "code": 40, "label": "(", "popup": [
{ "code": 91, "label": "[" },
{ "code": 123, "label": "{" }
] },
{ "code": 37, "label": "%" }
],
[
{ "code": 40, "label": "(", "popup": {
"relevant": [
{ "code": 91, "label": "[" },
{ "code": 123, "label": "{" }
]
} },
{ "code": 52, "label": "4", "type": "numeric" },
{ "code": 53, "label": "5", "type": "numeric" },
{ "code": 54, "label": "6", "type": "numeric" },
{ "code": 32, "label": " ", "popup": [] }
], [
{ "code": 41, "label": ")", "popup": [
{ "code": 93, "label": "]" },
{ "code": 125, "label": "}" }
] },
{ "code": 32, "label": "space" }
],
[
{ "code": 41, "label": ")", "popup": {
"relevant": [
{ "code": 93, "label": "]" },
{ "code": 125, "label": "}" }
]
} },
{ "code": 55, "label": "7", "type": "numeric" },
{ "code": 56, "label": "8", "type": "numeric" },
{ "code": 57, "label": "9", "type": "numeric" },
{ "code": -5, "label": "delete", "type": "enter_editing" }
], [
],
[
{ "code": -201, "label": "view_characters", "type": "system_gui" },
{ "code": 44, "label": ",", "popup": [] },
{ "code": 44, "label": "," },
{ "code": -202, "label": "view_symbols", "type": "system_gui" },
{ "code": 48, "label": "0", "type": "numeric" },
{ "code": 61, "label": "=", "popup": [] },
{ "code": 46, "label": ".", "popup": [] },
{ "code": 61, "label": "=" },
{ "code": 46, "label": "." },
{ "code": 10, "label": "enter", "type": "enter_editing" }
]
]

View File

@@ -1,29 +1,33 @@
{
"type": "numeric",
"name": "default",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
{ "code": 49, "label": "1", "type": "numeric" },
{ "code": 50, "label": "2", "type": "numeric" },
{ "code": 51, "label": "3", "type": "numeric" },
{ "code": 45, "label": "-", "popup": [] }
], [
{ "code": 45, "label": "-" }
],
[
{ "code": 52, "label": "4", "type": "numeric" },
{ "code": 53, "label": "5", "type": "numeric" },
{ "code": 54, "label": "6", "type": "numeric" },
{ "code": 32, "label": " ", "popup": [] }
], [
{ "code": 32, "label": "space" }
],
[
{ "code": 55, "label": "7", "type": "numeric" },
{ "code": 56, "label": "8", "type": "numeric" },
{ "code": 57, "label": "9", "type": "numeric" },
{ "code": -5, "label": "delete", "type": "enter_editing" }
], [
{ "code": 44, "label": ",", "popup": [] },
{ "code": 48, "label": "0", "type": "numeric", "popup": [
{ "code": 43, "label": "+" }
] },
{ "code": 46, "label": ".", "popup": [] },
],
[
{ "code": 44, "label": "," },
{ "code": 48, "label": "0", "type": "numeric", "popup": {
"main": { "code": 43, "label": "+" }
} },
{ "code": 46, "label": "." },
{ "code": 10, "label": "enter", "type": "enter_editing" }
]
]

View File

@@ -1,29 +1,33 @@
{
"type": "phone",
"name": "default",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
{ "code": 49, "label": "1", "type": "numeric" },
{ "code": 50, "label": "2", "type": "numeric" },
{ "code": 51, "label": "3", "type": "numeric" },
{ "code": 45, "label": "-", "popup": [] }
], [
{ "code": 45, "label": "-" }
],
[
{ "code": 52, "label": "4", "type": "numeric" },
{ "code": 53, "label": "5", "type": "numeric" },
{ "code": 54, "label": "6", "type": "numeric" },
{ "code": 32, "label": " ", "popup": [] }
], [
{ "code": 32, "label": "space" }
],
[
{ "code": 55, "label": "7", "type": "numeric" },
{ "code": 56, "label": "8", "type": "numeric" },
{ "code": 57, "label": "9", "type": "numeric" },
{ "code": -5, "label": "delete", "type": "enter_editing" }
], [
],
[
{ "code": -207, "label": "view_phone2", "type": "system_gui" },
{ "code": 48, "label": "0", "type": "numeric", "popup": [
{ "code": 43, "label": "+" }
] },
{ "code": 46, "label": ".", "popup": [] },
{ "code": 48, "label": "0", "type": "numeric", "popup": {
"main": { "code": 43, "label": "+" }
} },
{ "code": 46, "label": "." },
{ "code": 10, "label": "enter", "type": "enter_editing" }
]
]

View File

@@ -1,27 +1,31 @@
{
"type": "phone2",
"name": "default",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
{ "code": 40, "label": "(", "popup": [] },
{ "code": 47, "label": "/", "popup": [] },
{ "code": 41, "label": ")", "popup": [] },
{ "code": 45, "label": "-", "popup": [] }
], [
{ "code": 78, "label": "N", "popup": [] },
{ "code": 40, "label": "(" },
{ "code": 47, "label": "/" },
{ "code": 41, "label": ")" },
{ "code": 45, "label": "-" }
],
[
{ "code": 78, "label": "N" },
{ "code": 44, "label": "pause", "type": "system_gui" },
{ "code": 44, "label": ",", "popup": [] },
{ "code": 32, "label": " ", "popup": [] }
], [
{ "code": 42, "label": "*", "popup": [] },
{ "code": 44, "label": "," },
{ "code": 32, "label": "space" }
],
[
{ "code": 42, "label": "*" },
{ "code": 59, "label": "wait", "type": "system_gui" },
{ "code": 35, "label": "#", "popup": [] },
{ "code": 35, "label": "#" },
{ "code": -5, "label": "delete", "type": "enter_editing" }
], [
],
[
{ "code": -206, "label": "view_phone", "type": "system_gui" },
{ "code": 43, "label": "+", "popup": [] },
{ "code": 46, "label": ".", "popup": [] },
{ "code": 43, "label": "+" },
{ "code": 46, "label": "." },
{ "code": 10, "label": "enter", "type": "enter_editing" }
]
]

View File

@@ -1,19 +1,23 @@
{
"type": "symbols/mod",
"name": "default",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
{ "code": -203, "label": "view_symbols2", "type": "system_gui" },
{ "code": 0 },
{ "code": 0, "type": "placeholder" },
{ "code": -5, "label": "delete", "type": "enter_editing" }
], [
],
[
{ "code": -201, "label": "view_characters", "type": "system_gui" },
{ "code": 44, "label": ",", "popup": [] },
{ "code": 44, "label": "," },
{ "code": -205, "label": "view_numeric_advanced", "type": "system_gui" },
{ "code": 32, "label": " ", "popup": [] },
{ "code": 46, "label": ".", "popup": [] },
{ "code": 10, "label": "enter", "type": "enter_editing" }
{ "code": 32, "label": "space" },
{ "code": 46, "label": ".", "popup": {
"main": { "code": 8230, "label": "" }
} },
{ "code": 10, "label": "enter", "groupId": 3, "type": "enter_editing" }
]
]
}

View File

@@ -1,71 +1,96 @@
{
"type": "symbols",
"name": "western_default",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
{ "code": 64, "label": "@", "popup": [] },
{ "code": 35, "label": "#", "popup": [
{ "code": 8470, "label": "№" }
] },
{ "code": 36, "label": "$", "popup": [
{ "code": 8369, "label": "" },
{ "code": 8364, "label": "€" },
{ "code": 162, "label": "¢" },
{ "code": 163, "label": "£" },
{ "code": 165, "label": "¥" }
] },
{ "code": 95, "label": "_", "popup": [] },
{ "code": 38, "label": "&", "popup": [] },
{ "code": 45, "label": "-", "popup": [
{ "code": 8212, "label": "" },
{ "code": 95, "label": "_" },
{ "code": 8211, "label": "" },
{ "code": 183, "label": "·" }
] },
{ "code": 43, "label": "+", "popup": [
{ "code": 177, "label": "±" }
] },
{ "code": 40, "label": "(", "popup": [
{ "code": 91, "label": "[" },
{ "code": 60, "label": "<" },
{ "code": 123, "label": "{" }
] },
{ "code": 41, "label": ")", "popup": [
{ "code": 93, "label": "]" },
{ "code": 62, "label": ">" },
{ "code": 125, "label": "}" }
] },
{ "code": 47, "label": "/", "popup": [] }
], [
{ "code": 42, "label": "*", "popup": [
{ "code": 9733, "label": "" },
{ "code": 8224, "label": "†" },
{ "code": 8225, "label": "‡" }
] },
{ "code": 34, "label": "\"", "popup": [
{ "code": 8222, "label": "„" },
{ "code": 8220, "label": "" },
{ "code": 8221, "label": "" },
{ "code": 171, "label": "«" },
{ "code": 187, "label": "»" }
] },
{ "code": 39, "label": "'", "popup": [
{ "code": 8218, "label": "" },
{ "code": 8216, "label": "" },
{ "code": 8217, "label": "" },
{ "code": 8249, "label": "" },
{ "code": 8250, "label": "" }
] },
{ "code": 58, "label": ":", "popup": [] },
{ "code": 59, "label": ";", "popup": [] },
{ "code": 33, "label": "!", "popup": [
{ "code": 161, "label": "¡" }
] },
{ "code": 63, "label": "?", "popup": [
{ "code": 191, "label": "¿" },
{ "code": 8253, "label": "" }
] }
{ "code": 64, "label": "@" },
{ "code": 35, "label": "#", "popup": {
"main": { "code": 8470, "label": "№" }
} },
{ "code": 36, "label": "$", "popup": {
"main": { "code": 8364, "label": "" },
"relevant": [
{ "code": 8369, "label": "" },
{ "code": 162, "label": "¢" },
{ "code": 163, "label": "£" },
{ "code": 165, "label": "¥" }
]
} },
{ "code": 37, "label": "%", "popup": {
"main": { "code": 8240, "label": "" },
"relevant": [
{ "code": 8453, "label": "" }
]
} },
{ "code": 38, "label": "&" },
{ "code": 45, "label": "-", "popup": {
"main": { "code": 95, "label": "_" },
"relevant": [
{ "code": 8212, "label": "" },
{ "code": 8211, "label": "" },
{ "code": 183, "label": "·" }
]
} },
{ "code": 43, "label": "+", "popup": {
"main": { "code": 177, "label": "±" }
} },
{ "code": 40, "label": "(", "popup": {
"main": { "code": 60, "label": "<" },
"relevant": [
{ "code": 91, "label": "[" },
{ "code": 123, "label": "{" }
]
} },
{ "code": 41, "label": ")", "popup": {
"main": { "code": 62, "label": ">" },
"relevant": [
{ "code": 93, "label": "]" },
{ "code": 125, "label": "}" }
]
} },
{ "code": 47, "label": "/" }
],
[
{ "code": 42, "label": "*", "popup": {
"main": { "code": 8224, "label": "" },
"relevant": [
{ "code": 9733, "label": "" },
{ "code": 8225, "label": "‡" }
]
} },
{ "code": 34, "label": "\"", "popup": {
"main": { "code": 8221, "label": "" },
"relevant": [
{ "code": 8222, "label": "" },
{ "code": 8220, "label": "" },
{ "code": 171, "label": "«" },
{ "code": 187, "label": "»" }
]
} },
{ "code": 39, "label": "'", "popup": {
"main": { "code": 8217, "label": "" },
"relevant": [
{ "code": 8218, "label": "" },
{ "code": 8216, "label": "" },
{ "code": 8249, "label": "" },
{ "code": 8250, "label": "" }
]
} },
{ "code": 58, "label": ":", "popup": {
"main": { "code": 8942, "label": "⋮" }
} },
{ "code": 59, "label": ";" },
{ "code": 33, "label": "!", "popup": {
"main": { "code": 161, "label": "¡" }
} },
{ "code": 63, "label": "?", "popup": {
"main": { "code": 191, "label": "¿" },
"relevant": [
{ "code": 8253, "label": "‽" }
]
} }
]
]
}

View File

@@ -1,19 +1,21 @@
{
"type": "symbols2/mod",
"name": "default",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
{ "code": -202, "label": "view_symbols", "type": "system_gui" },
{ "code": 0 },
{ "code": 0, "type": "placeholder" },
{ "code": -5, "label": "delete", "type": "enter_editing" }
], [
],
[
{ "code": -201, "label": "view_characters", "type": "system_gui" },
{ "code": 60, "label": "<", "popup": [] },
{ "code": 60, "label": "<" },
{ "code": -205, "label": "view_numeric_advanced", "type": "system_gui" },
{ "code": 32, "label": " ", "popup": [] },
{ "code": 62, "label": ">", "popup": [] },
{ "code": 10, "label": "enter", "type": "enter_editing" }
{ "code": 32, "label": "space" },
{ "code": 62, "label": ">" },
{ "code": 10, "label": "enter", "groupId": 3, "type": "enter_editing" }
]
]
}

View File

@@ -1,69 +1,79 @@
{
"type": "symbols2",
"name": "western_default",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
{ "code": 126, "label": "~", "popup": [] },
{ "code": 96, "label": "`", "popup": [] },
{ "code": 124, "label": "|", "popup": [] },
{ "code": 8226, "label": "•", "popup": [
{ "code": 9827, "label": "" },
{ "code": 9824, "label": "♠" },
{ "code": 9834, "label": "" },
{ "code": 9829, "label": "" },
{ "code": 9830, "label": "" }
] },
{ "code": 8730, "label": "√", "popup": [] },
{ "code": 960, "label": "π", "popup": [
{ "code": 937, "label": "Ω" },
{ "code": 928, "label": "Π" },
{ "code": 956, "label": "μ" }
] },
{ "code": 247, "label": "÷", "popup": [] },
{ "code": 215, "label": "×", "popup": [] },
{ "code": 182, "label": "¶", "popup": [
{ "code": 167, "label": "§" }
] },
{ "code": 8710, "label": "∆", "popup": [] }
], [
{ "code": 163, "label": "£", "popup": [] },
{ "code": 162, "label": "¢", "popup": [] },
{ "code": 8364, "label": "€", "popup": [] },
{ "code": 165, "label": "¥", "popup": [] },
{ "code": 94, "label": "^", "popup": [
{ "code": 8592, "label": "" },
{ "code": 8593, "label": "" },
{ "code": 8595, "label": "" },
{ "code": 8594, "label": "" }
] },
{ "code": 176, "label": "°", "popup": [
{ "code": 8242, "label": "" },
{ "code": 8243, "label": "" }
] },
{ "code": 61, "label": "=", "popup": [
{ "code": 8734, "label": "∞" },
{ "code": 8800, "label": "≠" },
{ "code": 8776, "label": "" }
] },
{ "code": 123, "label": "{", "popup": [
{ "code": 40, "label": "(" }
] },
{ "code": 125, "label": "}", "popup": [
{ "code": 41, "label": ")" }
] },
{ "code": 92, "label": "\\", "popup": [] }
], [
{ "code": 37, "label": "%", "popup": [
{ "code": 8240, "label": "‰" },
{ "code": 8453, "label": "℅" }
] },
{ "code": 169, "label": "©", "popup": [] },
{ "code": 174, "label": "®", "popup": [] },
{ "code": 8482, "label": "", "popup": [] },
{ "code": 10003, "label": "", "popup": [] },
{ "code": 91, "label": "[", "popup": [] },
{ "code": 93, "label": "]", "popup": [] }
{ "code": 126, "label": "~" },
{ "code": 96, "label": "`" },
{ "code": 124, "label": "|" },
{ "code": 8226, "label": "•", "popup": {
"main": { "code": 9834, "label": "" },
"relevant": [
{ "code": 9827, "label": "" },
{ "code": 9824, "label": "" },
{ "code": 9829, "label": "" },
{ "code": 9830, "label": "♦" }
]
} },
{ "code": 8730, "label": "" },
{ "code": 960, "label": "π", "popup": {
"main": { "code": 928, "label": "Π" },
"relevant": [
{ "code": 937, "label": "Ω" },
{ "code": 956, "label": "μ" }
]
} },
{ "code": 247, "label": "÷" },
{ "code": 215, "label": "×" },
{ "code": 182, "label": "¶", "popup": {
"main": { "code": 167, "label": "§" }
} },
{ "code": 8710, "label": "∆" }
],
[
{ "code": 163, "label": "£" },
{ "code": 162, "label": "¢" },
{ "code": 8364, "label": "" },
{ "code": 165, "label": "¥" },
{ "code": 94, "label": "^", "popup": {
"main": { "code": 8593, "label": "↑" },
"relevant": [
{ "code": 8592, "label": "" },
{ "code": 8595, "label": "↓" },
{ "code": 8594, "label": "→" }
]
} },
{ "code": 176, "label": "°", "popup": {
"main": { "code": 8242, "label": "" },
"relevant": [
{ "code": 8243, "label": "" }
]
} },
{ "code": 61, "label": "=", "popup": {
"main": { "code": 8800, "label": "≠" },
"relevant": [
{ "code": 8734, "label": "∞" },
{ "code": 8776, "label": "≈" }
]
} },
{ "code": 123, "label": "{", "popup": {
"main": { "code": 40, "label": "(" }
} },
{ "code": 125, "label": "}", "popup": {
"main": { "code": 41, "label": ")" }
} },
{ "code": 92, "label": "\\" }
],
[
{ "code": 95, "label": "_" },
{ "code": 169, "label": "©" },
{ "code": 174, "label": "®" },
{ "code": 8482, "label": "™" },
{ "code": 10003, "label": "✓" },
{ "code": 91, "label": "[" },
{ "code": 93, "label": "]" }
]
]
}

View File

@@ -1,60 +1,64 @@
{
"$type": "dev.patrickgold.florisboard.ime.theme.Theme",
"name": "floris_day",
"displayName": "Floris Day",
"author": "patrickgold",
"label": "Floris Day",
"authors": [ "patrickgold" ],
"isNightTheme": false,
"attributes": {
"window": {
"colorPrimary": "#4CAF50",
"colorPrimaryDark": "#388E3C",
"colorAccent": "#FF9800",
"navigationBarColor": "@keyboard/bgColor",
"navigationBarColor": "@keyboard/background",
"navigationBarLight": "true",
"semiTransparentColor": "#20000000",
"textColor": "#000000"
},
"keyboard": {
"bgColor": "#E0E0E0"
"background": "#E0E0E0"
},
"key": {
"bgColor": "#FFFFFF",
"bgColorPressed": "#F5F5F5",
"fgColor": "@window/textColor"
"background": "#FFFFFF",
"backgroundPressed": "#F5F5F5",
"foreground": "@window/textColor",
"foregroundPressed": "@window/textColor",
"showBorder": "true"
},
"keyEnter": {
"bgColor": "@window/colorPrimary",
"bgColorPressed": "@window/colorPrimaryDark",
"fgColor": "#FFFFFF"
"key:enter": {
"background": "@window/colorPrimary",
"backgroundPressed": "@window/colorPrimaryDark",
"foreground": "#FFFFFF",
"foregroundPressed": "#FFFFFF"
},
"keyPopup": {
"bgColor": "#EEEEEE",
"bgColorActive": "#BDBDBD",
"fgColor": "@window/textColor"
},
"keyShift": {
"bgColor": "@key/bgColor",
"bgColorPressed": "@key/bgColorPressed",
"fgColor": "@window/textColor",
"fgColorCapsLock": "@window/colorAccent"
"key:shift:capslock": {
"foreground": "@window/colorAccent",
"foregroundPressed": "@window/colorAccent"
},
"media": {
"fgColor": "@window/textColor",
"fgColorAlt": "#757575"
"foreground": "@window/textColor",
"foregroundAlt": "#757575"
},
"oneHanded": {
"bgColor": "#E8F5E9"
"background": "#E8F5E9",
"foreground": "#424242"
},
"oneHandedButton": {
"fgColor": "#424242"
"popup": {
"background": "#EEEEEE",
"backgroundActive": "#BDBDBD",
"foreground": "@window/textColor"
},
"privateMode": {
"background": "#A000FF",
"foreground": "#FFFFFF"
},
"smartbar": {
"bgColor": "transparent",
"fgColor": "@window/textColor",
"fgColorAlt": "#8A8A8A"
"background": "transparent",
"foreground": "@window/textColor",
"foregroundAlt": "#8A8A8A"
},
"smartbarButton": {
"bgColor": "@key/bgColor",
"fgColor": "@key/fgColor"
"background": "@key/background",
"foreground": "@key/foreground"
}
}
}

View File

@@ -0,0 +1,68 @@
{
"$type": "dev.patrickgold.florisboard.ime.theme.Theme",
"name": "floris_day_borderless",
"label": "Floris Day Borderless",
"authors": [ "patrickgold" ],
"isNightTheme": false,
"attributes": {
"window": {
"colorPrimary": "#4CAF50",
"colorPrimaryDark": "#388E3C",
"colorAccent": "#FF9800",
"navigationBarColor": "@keyboard/background",
"navigationBarLight": "true",
"semiTransparentColor": "#20000000",
"textColor": "#000000"
},
"keyboard": {
"background": "#E0E0E0"
},
"key": {
"background": "transparent",
"backgroundPressed": "#7FF5F5F5",
"foreground": "@window/textColor",
"foregroundPressed": "@window/textColor",
"showBorder": "false"
},
"key:enter": {
"background": "@window/colorPrimary",
"backgroundPressed": "@window/colorPrimaryDark",
"foreground": "#FFFFFF",
"foregroundPressed": "#FFFFFF"
},
"key:shift:capslock": {
"foreground": "@window/colorAccent",
"foregroundPressed": "@window/colorAccent"
},
"key:space": {
"background": "#7FF5F5F5",
"backgroundPressed": "#FFF5F5F5"
},
"media": {
"foreground": "@window/textColor",
"foregroundAlt": "#757575"
},
"oneHanded": {
"background": "#E8F5E9",
"foreground": "#424242"
},
"popup": {
"background": "#EEEEEE",
"backgroundActive": "#BDBDBD",
"foreground": "@window/textColor"
},
"privateMode": {
"background": "#A000FF",
"foreground": "#FFFFFF"
},
"smartbar": {
"background": "transparent",
"foreground": "@window/textColor",
"foregroundAlt": "#8A8A8A"
},
"smartbarButton": {
"background": "#FFFFFF",
"foreground": "@window/textColor"
}
}
}

View File

@@ -1,60 +1,64 @@
{
"$type": "dev.patrickgold.florisboard.ime.theme.Theme",
"name": "floris_night",
"displayName": "Floris Night",
"author": "patrickgold",
"label": "Floris Night",
"authors": [ "patrickgold" ],
"isNightTheme": true,
"attributes": {
"window": {
"colorPrimary": "#4CAF50",
"colorPrimaryDark": "#388E3C",
"colorAccent": "#FF9800",
"navigationBarColor": "@keyboard/bgColor",
"navigationBarColor": "@keyboard/background",
"navigationBarLight": "false",
"semiTransparentColor": "#20FFFFFF",
"textColor": "#FFFFFF"
},
"keyboard": {
"bgColor": "#212121"
"background": "#212121"
},
"key": {
"bgColor": "#424242",
"bgColorPressed": "#616161",
"fgColor": "@window/textColor"
"background": "#424242",
"backgroundPressed": "#616161",
"foreground": "@window/textColor",
"foregroundPressed": "@window/textColor",
"showBorder": "true"
},
"keyEnter": {
"bgColor": "@window/colorPrimary",
"bgColorPressed": "@window/colorPrimaryDark",
"fgColor": "#FFFFFF"
"key:enter": {
"background": "@window/colorPrimary",
"backgroundPressed": "@window/colorPrimaryDark",
"foreground": "#FFFFFF",
"foregroundPressed": "#FFFFFF"
},
"keyPopup": {
"bgColor": "#757575",
"bgColorActive": "#BDBDBD",
"fgColor": "@window/textColor"
},
"keyShift": {
"bgColor": "@key/bgColor",
"bgColorPressed": "@key/bgColorPressed",
"fgColor": "@window/textColor",
"fgColorCapsLock": "@window/colorAccent"
"key:shift:capslock": {
"foreground": "@window/colorAccent",
"foregroundPressed": "@window/colorAccent"
},
"media": {
"fgColor": "@window/textColor",
"fgColorAlt": "#BDBDBD"
"foreground": "@window/textColor",
"foregroundAlt": "#BDBDBD"
},
"oneHanded": {
"bgColor": "#1B5E20"
"background": "#1B5E20",
"foreground": "#EEEEEE"
},
"oneHandedButton": {
"fgColor": "#EEEEEE"
"popup": {
"background": "#757575",
"backgroundActive": "#BDBDBD",
"foreground": "@window/textColor"
},
"privateMode": {
"background": "#A000FF",
"foreground": "#FFFFFF"
},
"smartbar": {
"bgColor": "transparent",
"fgColor": "@window/textColor",
"fgColorAlt": "#73FFFFFF"
"background": "transparent",
"foreground": "@window/textColor",
"foregroundAlt": "#73FFFFFF"
},
"smartbarButton": {
"bgColor": "@key/bgColor",
"fgColor": "@key/fgColor"
"background": "@key/background",
"foreground": "@key/foreground"
}
}
}

View File

@@ -0,0 +1,68 @@
{
"$type": "dev.patrickgold.florisboard.ime.theme.Theme",
"name": "floris_night_borderless",
"label": "Floris Night Borderless",
"authors": [ "patrickgold" ],
"isNightTheme": true,
"attributes": {
"window": {
"colorPrimary": "#4CAF50",
"colorPrimaryDark": "#388E3C",
"colorAccent": "#FF9800",
"navigationBarColor": "@keyboard/background",
"navigationBarLight": "false",
"semiTransparentColor": "#20FFFFFF",
"textColor": "#FFFFFF"
},
"keyboard": {
"background": "#212121"
},
"key": {
"background": "transparent",
"backgroundPressed": "#7F616161",
"foreground": "@window/textColor",
"foregroundPressed": "@window/textColor",
"showBorder": "false"
},
"key:enter": {
"background": "@window/colorPrimary",
"backgroundPressed": "@window/colorPrimaryDark",
"foreground": "#FFFFFF",
"foregroundPressed": "#FFFFFF"
},
"key:shift:capslock": {
"foreground": "@window/colorAccent",
"foregroundPressed": "@window/colorAccent"
},
"key:space": {
"background": "#2F616161",
"backgroundPressed": "#7F616161"
},
"media": {
"foreground": "@window/textColor",
"foregroundAlt": "#BDBDBD"
},
"oneHanded": {
"background": "#1B5E20",
"foreground": "#EEEEEE"
},
"popup": {
"background": "#757575",
"backgroundActive": "#BDBDBD",
"foreground": "@window/textColor"
},
"privateMode": {
"background": "#A000FF",
"foreground": "#FFFFFF"
},
"smartbar": {
"background": "transparent",
"foreground": "@window/textColor",
"foregroundAlt": "#73FFFFFF"
},
"smartbarButton": {
"background": "#424242",
"foreground": "@window/textColor"
}
}
}

View File

@@ -61,6 +61,30 @@ limitations under the License.
<hr>
<h3>ExpandableFab</h3>
<span>Copyright (c) 2020 Kelvin Abumere and The Nambi Company</span>
<pre>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
</pre>
<hr>
<h3>FlexboxLayout</h3>
<span>Copyright 2018 Google LLC</span>
<pre>
@@ -479,6 +503,24 @@ limitations under the License.
<hr>
<h3>kotlin-result</h3>
<span>Copyright (c) 2017-2020 Michael Bull (https://www.michael-bull.com)</span>
<pre>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
</pre>
<hr>
<h3>Material Icons</h3>
<span>Copyright 2018 Google LLC</span>
<pre>
@@ -696,6 +738,24 @@ You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
</pre>
<hr>
<h3>Timber</h3>
<span>Copyright 2013 Jake Wharton</span>
<pre>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

View File

@@ -0,0 +1,60 @@
/*
* Copyright (C) 2020 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.crashutility
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.databinding.CrashDialogBinding
class CrashDialogActivity : AppCompatActivity() {
private lateinit var binding: CrashDialogBinding
private var stacktrace: String = ""
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = CrashDialogBinding.inflate(layoutInflater)
setContentView(binding.root)
stacktrace = CrashUtility.getUnhandledStacktrace(this)
binding.stacktrace.text = stacktrace
binding.copyToClipboard.setOnClickListener {
val clipboardManager = getSystemService(Context.CLIPBOARD_SERVICE)
if (clipboardManager != null && clipboardManager is ClipboardManager) {
clipboardManager.setPrimaryClip(ClipData.newPlainText(stacktrace, stacktrace))
}
}
binding.openBugReportForm.setOnClickListener {
val browserIntent = Intent(
Intent.ACTION_VIEW,
Uri.parse(resources.getString(R.string.florisboard__issue_tracker_new_issue_url))
)
startActivity(browserIntent)
}
binding.close.setOnClickListener {
finish()
}
}
}

View File

@@ -0,0 +1,401 @@
/*
* Copyright (C) 2020 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.crashutility
import android.annotation.SuppressLint
import android.app.*
import android.app.Application.ActivityLifecycleCallbacks
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.os.Process
import android.util.Log
import android.view.inputmethod.InputMethodManager
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.core.FlorisBoard
import timber.log.Timber
import java.io.File
import java.lang.ref.WeakReference
import kotlin.system.exitProcess
/**
* Abstract class which holds several static methods used for handling unexpected errors.
*
* Parts of this class (especially the install() function and the uncaughtException() handler) have
* been inspired by the great CustomActivityOnCrash library:
* https://github.com/Ereza/CustomActivityOnCrash (licensed under Apache 2.0)
* https://github.com/Ereza/CustomActivityOnCrash/blob/master/library/src/main/java/cat/ereza/customactivityoncrash/CustomActivityOnCrash.java
*/
abstract class CrashUtility private constructor() {
companion object {
private const val SHARED_PREFS_FILE = "crash_utility"
private const val SHARED_PREFS_LAST_CRASH_TIMESTAMP = "last_crash_timestamp"
private const val NOTIFICATION_CHANNEL_ID = "dev.patrickgold.florisboard.crashutility"
private const val NOTIFICATION_ID = 0xFBAD0100
private const val UNHANDLED_STACKTRACE_FILE_EXT = "stacktrace"
private var lastActivityCreated: WeakReference<Activity?> = WeakReference(null)
/**
* Installs the CrashUtility crash handler for the given package [context]. Also registers
* a notification channel for devices with Android 8.0+.
*
* @param context The current package context. If null is supplied, this function does
* nothing.
* @return True if the installation was successful, false otherwise.
*/
fun install(context: Context?): Boolean {
if (context == null) {
Timber.e(
"install($context): Can't install crash handler with a null Context object, doing nothing!"
)
return false
}
val oldHandler = Thread.getDefaultUncaughtExceptionHandler()
if (oldHandler is UncaughtExceptionHandler) {
Timber.i("install($context): Crash handler is already installed, doing nothing!")
} else {
val application = context.applicationContext
if (application != null && application is Application) {
try {
Thread.setDefaultUncaughtExceptionHandler(
UncaughtExceptionHandler(
WeakReference(application),
WeakReference(oldHandler),
application.filesDir.absolutePath
)
)
Timber.i(
"install($context): Successfully installed crash handler for this application!"
)
} catch (e: SecurityException) {
Timber.e(
"install($context): Failed to install crash handler, probably due to missing runtime permission 'setDefaultUncaughtExceptionHandler':\n$e"
)
return false
} catch (e: Exception) {
Timber.e(
"install($context): Failed to install crash handler due to an unspecified error:\n$e"
)
return false
}
application.registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
override fun onActivityCreated(
activity: Activity,
savedInstanceState: Bundle?
) {
if (activity !is CrashDialogActivity) {
lastActivityCreated = WeakReference(activity)
}
}
override fun onActivityStarted(activity: Activity) {}
override fun onActivityResumed(activity: Activity) {}
override fun onActivityPaused(activity: Activity) {}
override fun onActivityStopped(activity: Activity) {}
override fun onActivitySaveInstanceState(
activity: Activity,
outState: Bundle
) {}
override fun onActivityDestroyed(activity: Activity) {}
})
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
try {
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE)
if (notificationManager != null && notificationManager is NotificationManager) {
val notificationChannel = NotificationChannel(
NOTIFICATION_CHANNEL_ID,
context.resources.getString(R.string.crash_notification_channel__title),
NotificationManager.IMPORTANCE_HIGH
)
notificationManager.createNotificationChannel(notificationChannel)
}
Timber.i(
"install($context): Successfully created crash handler notification channel!"
)
} catch (e: Exception) {
Timber.e(
"install($context): Failed to create crash handler notification channel due to an unspecified error:\n$e"
)
}
}
} else {
Timber.e(
"install($context): Can't install crash handler with a null Application object, doing nothing!"
)
return false
}
}
return true
}
/**
* Reads and returns all unhandled stacktrace files.
*
* @param context The current package context. If null is supplied, this function returns
* an empty string.
* @return All unhandled stacktrace files or an empty string.
*/
fun getUnhandledStacktrace(context: Context?): String {
context ?: return ""
val retString: StringBuilder = StringBuilder()
val ustDir = getUstDir(context)
if (ustDir.isDirectory) {
(ustDir.listFiles { pathname ->
pathname.name.endsWith(".$UNHANDLED_STACKTRACE_FILE_EXT")
})?.forEach { file ->
val newLine = System.lineSeparator()
Timber.i("Reading unhandled stacktrace: ${file.name}")
retString.append("~~~ ${file.name} ~~~$newLine$newLine")
retString.append(readFile(file))
file.delete()
}
}
return retString.toString()
}
fun hasUnhandledStacktraceFiles(context: Context): Boolean {
val ustDir = getUstDir(context)
return if (ustDir.isDirectory) {
(ustDir.listFiles { pathname ->
pathname.name.endsWith(".$UNHANDLED_STACKTRACE_FILE_EXT")
})?.isNotEmpty() ?: false
} else {
false
}
}
/**
* Gets the last crash timestamp from the shared preferences.
*
* @param context The current package context. If null is supplied, this function returns
* the default value for the timestamp (0).
* @return The last time crash timestamp or 0.
*/
private fun getLastCrashTimestamp(context: Context?): Long {
context ?: return 0
return context.getSharedPreferences(SHARED_PREFS_FILE, Context.MODE_PRIVATE)
.getLong(SHARED_PREFS_LAST_CRASH_TIMESTAMP, 0)
}
/**
* Sets the last crash timestamp in the shared preferences.
*
* @param context The current package context. If null is supplied, this function does
* nothing.
* @param value The timestamp of the current crash.
*/
@SuppressLint("ApplySharedPref")
private fun setLastCrashTimestamp(context: Context?, value: Long) {
context ?: return
// Note: must use commit() instead of apply(), as the value must be immediately written
// to be possibly instantly read again.
context.getSharedPreferences(SHARED_PREFS_FILE, Context.MODE_PRIVATE)
.edit()
.putLong(SHARED_PREFS_LAST_CRASH_TIMESTAMP, value)
.commit()
}
/**
* Gets a reference to the current unhandled stacktrace directory.
*
* @param context The current package context.
* @return The File object for the directory.
*/
private fun getUstDir(context: Context): File {
val path = context.filesDir.absolutePath
return File(path)
}
/**
* Gets a reference to the stacktrace file for given [timestamp].
*
* @param context The current package context.
* @param timestamp The timestamp of the stacktrace file to get.
* @return The File object for the stacktrace file.
*/
private fun getUstFile(context: Context, timestamp: Long): File {
val path = context.filesDir.absolutePath
return File("$path/$timestamp.$UNHANDLED_STACKTRACE_FILE_EXT")
}
/**
* Push a notification which opens [CrashDialogActivity] with given parameters.
*
* @param context The current package context. If null is supplied, this function does
* nothing.
* @param id The ID of the notification.
* @param title The title of the notification.
* @param body The body of the notification.
*/
private fun pushNotification(context: Context?, id: Int, title: String, body: String) {
context ?: return
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE)
if (notificationManager != null && notificationManager is NotificationManager) {
val notificationBuilder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Notification.Builder(context.applicationContext, NOTIFICATION_CHANNEL_ID)
} else {
@Suppress("DEPRECATION")
Notification.Builder(context.applicationContext).apply {
setPriority(Notification.PRIORITY_MAX)
}
}
val crashDialogIntent = Intent(context, CrashDialogActivity::class.java)
val notification = notificationBuilder.run {
setContentTitle(title)
style = Notification.BigTextStyle().bigText(body)
setContentText(body)
setSmallIcon(android.R.drawable.stat_notify_error)
setContentIntent(PendingIntent.getActivity(context, 0, crashDialogIntent, 0)).setAutoCancel(
true
)
build()
}
notificationManager.notify(id, notification)
}
}
/**
* Push a notification configured for a single crash.
*
* @param context The current package context. If null is supplied, this function does
* nothing.
*/
private fun pushCrashOnceNotification(context: Context?) {
context ?: return
pushNotification(
context,
NOTIFICATION_ID.toInt(),
context.resources.getString(R.string.crash_once_notification__title),
context.resources.getString(R.string.crash_once_notification__body)
)
}
/**
* Push a notification configured for multiple crashes.
*
* @param context The current package context. If null is supplied, this function does
* nothing.
*/
private fun pushCrashMultipleNotification(context: Context?) {
context ?: return
pushNotification(
context,
NOTIFICATION_ID.toInt(),
context.resources.getString(R.string.crash_multiple_notification__title),
context.resources.getString(R.string.crash_multiple_notification__body)
)
}
/**
* Reads a given [file] and returns its content.
*
* @param file The file object.
* @return The contents of the file or an empty string, if the file does not exist.
*/
private fun readFile(file: File): String {
val retText = StringBuilder()
if (file.exists()) {
val newLine = System.lineSeparator()
file.forEachLine {
retText.append(it)
retText.append(newLine)
}
}
return retText.toString()
}
/**
* Writes given [text] to given [file]. If the file already exists, its current content
* will be overwritten.
*
* @param file The file object.
* @param text The text to write to the file.
* @return The contents of the file or an empty string, if the file does not exist.
*/
private fun writeToFile(file: File, text: String) {
try {
file.writeText(text)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
/**
* Custom UncaughtExceptionHandler, which writes the captured stacktrace of the crash to the
* internal storage, pushes a crash notification and kills the current process.
*/
class UncaughtExceptionHandler(
private val application: WeakReference<Application>,
private val oldHandler: WeakReference<Thread.UncaughtExceptionHandler?>,
private val path: String
) : Thread.UncaughtExceptionHandler {
override fun uncaughtException(thread: Thread?, throwable: Throwable?) {
Timber.e("Detected application crash, executing custom crash handler.")
thread ?: return
throwable ?: return
val timestamp = System.currentTimeMillis()
val stacktrace = Log.getStackTraceString(throwable)
val ustFile = File("$path/$timestamp.$UNHANDLED_STACKTRACE_FILE_EXT")
writeToFile(ustFile, stacktrace)
val application = application.get()
if (application != null) {
val lastTimestamp = getLastCrashTimestamp(application)
if (lastTimestamp > 0) {
val lastFile = getUstFile(application, lastTimestamp)
val lastStacktrace = readFile(lastFile)
if (lastStacktrace == stacktrace) {
// Delete last stacktrace if it matches previous unhandled one
lastFile.delete()
}
}
setLastCrashTimestamp(application, timestamp)
if (timestamp - lastTimestamp < 5000) {
pushCrashMultipleNotification(application)
val florisboard = FlorisBoard.getInstanceOrNull()
if (florisboard != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
florisboard.switchToPreviousInputMethod()
} else {
val imm = application.getSystemService(Context.INPUT_METHOD_SERVICE)
if (imm != null && imm is InputMethodManager) {
@Suppress("DEPRECATION")
imm.switchToNextInputMethod(
florisboard.window?.window?.attributes?.token,
false
)
}
}
}
} else {
pushCrashOnceNotification(application)
}
}
val lastActivity = lastActivityCreated.get()
if (lastActivity != null) {
//oldHandler.get()?.uncaughtException(thread, throwable)
lastActivity.finish()
lastActivityCreated.clear()
}
Process.killProcess(Process.myPid())
exitProcess(10)
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,38 @@
/*
* Copyright (C) 2020 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.ime.core
import android.app.Application
import dev.patrickgold.florisboard.BuildConfig
import dev.patrickgold.florisboard.crashutility.CrashUtility
import dev.patrickgold.florisboard.ime.extension.AssetManager
import dev.patrickgold.florisboard.ime.theme.ThemeManager
import timber.log.Timber
class FlorisApplication : Application() {
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree())
}
CrashUtility.install(this)
val prefHelper = PrefHelper.getDefaultInstance(this)
val assetManager = AssetManager.init(this)
ThemeManager.init(this, assetManager, prefHelper)
prefHelper.initDefaultPreferences()
}
}

View File

@@ -22,28 +22,33 @@ import android.content.Context
import android.content.Intent
import android.content.res.ColorStateList
import android.content.res.Configuration
import android.graphics.Color
import android.inputmethodservice.InputMethodService
import android.media.AudioManager
import android.os.*
import android.provider.Settings
import android.util.Log
import android.view.Gravity
import android.view.View
import android.view.WindowManager
import android.view.inputmethod.CursorAnchorInfo
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputConnection
import android.widget.ImageButton
import android.view.inputmethod.InputMethodManager
import com.squareup.moshi.Json
import dev.patrickgold.florisboard.BuildConfig
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.media.MediaInputManager
import dev.patrickgold.florisboard.ime.popup.PopupLayerView
import dev.patrickgold.florisboard.ime.text.TextInputManager
import dev.patrickgold.florisboard.ime.text.gestures.SwipeAction
import dev.patrickgold.florisboard.ime.text.key.KeyCode
import dev.patrickgold.florisboard.ime.text.key.KeyData
import dev.patrickgold.florisboard.ime.text.keyboard.KeyboardMode
import dev.patrickgold.florisboard.ime.theme.Theme
import dev.patrickgold.florisboard.ime.theme.ThemeManager
import dev.patrickgold.florisboard.settings.SettingsMainActivity
import dev.patrickgold.florisboard.util.*
import timber.log.Timber
import java.lang.ref.WeakReference
/**
* Variable which holds the current [FlorisBoard] instance. To get this instance from another
@@ -55,7 +60,8 @@ private var florisboardInstance: FlorisBoard? = null
* Core class responsible to link together both the text and media input managers as well as
* managing the one-handed UI.
*/
class FlorisBoard : InputMethodService() {
class FlorisBoard : InputMethodService(), ClipboardManager.OnPrimaryClipChangedListener,
ThemeManager.OnThemeUpdatedListener {
lateinit var prefs: PrefHelper
private set
@@ -63,18 +69,25 @@ class FlorisBoard : InputMethodService() {
get() = inputWindowView?.context ?: this
var inputView: InputView? = null
private set
var popupLayerView: PopupLayerView? = null
private set
private var inputWindowView: InputWindowView? = null
private var eventListeners: MutableList<EventListener> = mutableListOf()
private var eventListeners: MutableList<WeakReference<EventListener?>?> = mutableListOf()
private var audioManager: AudioManager? = null
private var imeManager:InputMethodManager? = null
var clipboardManager: ClipboardManager? = null
private val themeManager: ThemeManager = ThemeManager.default()
private var vibrator: Vibrator? = null
private val osHandler = Handler()
var activeEditorInstance: EditorInstance = EditorInstance.default()
lateinit var subtypeManager: SubtypeManager
lateinit var activeSubtype: Subtype
private var currentThemeIsNight: Boolean = false
private var currentThemeResId: Int = 0
private var isNumberRowVisible: Boolean = false
val textInputManager: TextInputManager
val mediaInputManager: MediaInputManager
@@ -88,14 +101,22 @@ class FlorisBoard : InputMethodService() {
companion object {
private const val IME_ID: String = "dev.patrickgold.florisboard/.ime.core.FlorisBoard"
private const val IME_ID_DEBUG: String = "dev.patrickgold.florisboard.debug/dev.patrickgold.florisboard.ime.core.FlorisBoard"
fun checkIfImeIsEnabled(context: Context): Boolean {
val activeImeIds = Settings.Secure.getString(
context.contentResolver,
Settings.Secure.ENABLED_INPUT_METHODS
)
if (BuildConfig.DEBUG) Log.i(FlorisBoard::class.simpleName, "List of active IMEs: $activeImeIds")
return activeImeIds.split(":").contains(IME_ID)
Timber.i("List of active IMEs: $activeImeIds")
return when {
BuildConfig.DEBUG -> {
activeImeIds.split(":").contains(IME_ID_DEBUG)
}
else -> {
activeImeIds.split(":").contains(IME_ID)
}
}
}
fun checkIfImeIsSelected(context: Context): Boolean {
@@ -103,8 +124,15 @@ class FlorisBoard : InputMethodService() {
context.contentResolver,
Settings.Secure.DEFAULT_INPUT_METHOD
)
if (BuildConfig.DEBUG) Log.i(FlorisBoard::class.simpleName, "Selected IME: $selectedImeId")
return selectedImeId == IME_ID
Timber.i("Selected IME: $selectedImeId")
return when {
BuildConfig.DEBUG -> {
selectedImeId == IME_ID_DEBUG
}
else -> {
selectedImeId == IME_ID
}
}
}
@Synchronized
@@ -144,10 +172,12 @@ class FlorisBoard : InputMethodService() {
.build()
)
}
if (BuildConfig.DEBUG) Log.i(this::class.simpleName, "onCreate()")
Timber.i("onCreate()")
imeManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
clipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
clipboardManager?.addPrimaryClipChangedListener(this)
vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
prefs = PrefHelper.getDefaultInstance(this)
prefs.initDefaultPreferences()
@@ -155,89 +185,123 @@ class FlorisBoard : InputMethodService() {
subtypeManager = SubtypeManager(this, prefs)
activeSubtype = subtypeManager.getActiveSubtype() ?: Subtype.DEFAULT
currentThemeIsNight = prefs.internal.themeCurrentIsNight
currentThemeIsNight = themeManager.activeTheme.isNightTheme
currentThemeResId = getDayNightBaseThemeId(currentThemeIsNight)
isNumberRowVisible = prefs.keyboard.numberRow
setTheme(currentThemeResId)
updateTheme()
themeManager.registerOnThemeUpdatedListener(this)
AppVersionUtils.updateVersionOnInstallAndLastUse(this, prefs)
super.onCreate()
eventListeners.toList().forEach { it.onCreate() }
eventListeners.toList().forEach { it?.get()?.onCreate() }
}
@SuppressLint("InflateParams")
override fun onCreateInputView(): View? {
if (BuildConfig.DEBUG) Log.i(this::class.simpleName, "onCreateInputView()")
Timber.i("onCreateInputView()")
baseContext.setTheme(currentThemeResId)
inputWindowView = layoutInflater.inflate(R.layout.florisboard, null) as InputWindowView
popupLayerView = inputWindowView?.findViewById(R.id.popup_layer)
eventListeners.toList().forEach { it.onCreateInputView() }
eventListeners.toList().forEach { it?.get()?.onCreateInputView() }
return inputWindowView
}
fun registerInputView(inputView: InputView) {
if (BuildConfig.DEBUG) Log.i(this::class.simpleName, "registerInputView(inputView)")
Timber.i("registerInputView($inputView)")
this.inputView = inputView
initializeOneHandedEnvironment()
updateTheme()
updateSoftInputWindowLayoutParameters()
updateOneHandedPanelVisibility()
themeManager.notifyCallbackReceivers()
setActiveInput(R.id.text_input)
eventListeners.toList().forEach { it.onRegisterInputView(inputView) }
eventListeners.toList().forEach { it?.get()?.onRegisterInputView(inputView) }
}
override fun onDestroy() {
if (BuildConfig.DEBUG) Log.i(this::class.simpleName, "onDestroy()")
Timber.i("onDestroy()")
themeManager.unregisterOnThemeUpdatedListener(this)
clipboardManager?.removePrimaryClipChangedListener(this)
osHandler.removeCallbacksAndMessages(null)
florisboardInstance = null
eventListeners.toList().forEach { it.onDestroy() }
eventListeners.toList().forEach { it?.get()?.onDestroy() }
eventListeners.clear()
super.onDestroy()
}
override fun onStartInput(attribute: EditorInfo?, restarting: Boolean) {
Timber.i("onStartInput($attribute, $restarting)")
super.onStartInput(attribute, restarting)
currentInputConnection?.requestCursorUpdates(InputConnection.CURSOR_UPDATE_IMMEDIATE)
}
override fun onStartInputView(info: EditorInfo?, restarting: Boolean) {
currentInputConnection?.requestCursorUpdates(InputConnection.CURSOR_UPDATE_MONITOR)
Timber.i("onStartInputView($info, $restarting)")
Timber.i("onStartInputView: ${info?.debugSummarize()}")
super.onStartInputView(info, restarting)
eventListeners.toList().forEach { it.onStartInputView(info, restarting) }
activeEditorInstance = EditorInstance.from(info, this)
themeManager.updateRemoteColorValues(activeEditorInstance.packageName)
eventListeners.toList().forEach {
it?.get()?.onStartInputView(activeEditorInstance, restarting)
}
}
override fun onFinishInputView(finishingInput: Boolean) {
currentInputConnection?.requestCursorUpdates(0)
Timber.i( "onFinishInputView($finishingInput)")
if (finishingInput) {
activeEditorInstance = EditorInstance.default()
}
super.onFinishInputView(finishingInput)
eventListeners.toList().forEach { it.onFinishInputView(finishingInput) }
eventListeners.toList().forEach { it?.get()?.onFinishInputView(finishingInput) }
}
override fun onFinishInput() {
Timber.i("onFinishInput()")
super.onFinishInput()
currentInputConnection?.requestCursorUpdates(0)
}
override fun onWindowShown() {
if (BuildConfig.DEBUG) Log.i(this::class.simpleName, "onWindowShown()")
Timber.i("onWindowShown()")
prefs.sync()
updateTheme()
val newIsNumberRowVisible = prefs.keyboard.numberRow
if (isNumberRowVisible != newIsNumberRowVisible) {
textInputManager.layoutManager.clearLayoutCache(KeyboardMode.CHARACTERS)
isNumberRowVisible = newIsNumberRowVisible
}
themeManager.update()
updateOneHandedPanelVisibility()
activeSubtype = subtypeManager.getActiveSubtype() ?: Subtype.DEFAULT
onSubtypeChanged(activeSubtype)
setActiveInput(R.id.text_input)
super.onWindowShown()
eventListeners.toList().forEach { it.onWindowShown() }
eventListeners.toList().forEach { it?.get()?.onWindowShown() }
}
override fun onWindowHidden() {
if (BuildConfig.DEBUG) Log.i(this::class.simpleName, "onWindowHidden()")
Timber.i("onWindowHidden()")
super.onWindowHidden()
eventListeners.toList().forEach { it.onWindowHidden() }
eventListeners.toList().forEach { it?.get()?.onWindowHidden() }
}
override fun onConfigurationChanged(newConfig: Configuration) {
Timber.i("onConfigurationChanged($newConfig)")
if (isInputViewShown) {
updateOneHandedPanelVisibility()
}
@@ -245,73 +309,73 @@ class FlorisBoard : InputMethodService() {
super.onConfigurationChanged(newConfig)
}
override fun onUpdateCursorAnchorInfo(cursorAnchorInfo: CursorAnchorInfo?) {
super.onUpdateCursorAnchorInfo(cursorAnchorInfo)
eventListeners.toList().forEach { it.onUpdateCursorAnchorInfo(cursorAnchorInfo) }
}
override fun onUpdateSelection(
oldSelStart: Int,
oldSelEnd: Int,
newSelStart: Int,
newSelEnd: Int,
candidatesStart: Int,
candidatesEnd: Int
oldSelStart: Int, oldSelEnd: Int,
newSelStart: Int, newSelEnd: Int,
candidatesStart: Int, candidatesEnd: Int
) {
Timber.i("onUpdateSelection($oldSelStart, $oldSelEnd, $newSelStart, $newSelEnd, $candidatesStart, $candidatesEnd)")
super.onUpdateSelection(
oldSelStart,
oldSelEnd,
newSelStart,
newSelEnd,
candidatesStart,
candidatesEnd
oldSelStart, oldSelEnd,
newSelStart, newSelEnd,
candidatesStart, candidatesEnd
)
eventListeners.toList().forEach {
it.onUpdateSelection(
oldSelStart,
oldSelEnd,
newSelStart,
newSelEnd,
candidatesStart,
candidatesEnd
)
}
activeEditorInstance.onUpdateSelection(
oldSelStart, oldSelEnd,
newSelStart, newSelEnd
)
eventListeners.toList().forEach { it?.get()?.onUpdateSelection() }
}
/**
* Reapplies the supplies colors and settings from prefs to navigation bar.
*/
private fun updateTheme() {
val newThemeIsNightMode = prefs.internal.themeCurrentIsNight
override fun onThemeUpdated(theme: Theme) {
// Rebuild the UI if the theme has changed from day to night or vice versa to prevent
// theme glitches with scrollbars and hints of buttons in the media UI. If the UI must be
// rebuild, quit this method, as it will be called again by the newly created UI.
val newThemeIsNightMode = theme.isNightTheme
if (currentThemeIsNight != newThemeIsNightMode) {
currentThemeResId = getDayNightBaseThemeId(newThemeIsNightMode)
currentThemeIsNight = newThemeIsNightMode
setInputView(onCreateInputView())
return
}
// Get Window and the flags of the DecorView
val w = window?.window ?: return
inputView?.setBackgroundColor(prefs.theme.keyboardBgColor)
w.navigationBarColor = prefs.theme.navBarColor
var flags = w.decorView.systemUiVisibility
// Update navigation bar theme
w.navigationBarColor = theme.getAttr(Theme.Attr.WINDOW_NAVIGATION_BAR_COLOR).toSolidColor().color
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
var flags = w.decorView.systemUiVisibility
flags = if (prefs.theme.navBarIsLight) {
flags = if (theme.getAttr(Theme.Attr.WINDOW_NAVIGATION_BAR_LIGHT).toOnOff().state) {
flags or View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
} else {
flags and View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR.inv()
}
w.decorView.systemUiVisibility = flags
}
inputView?.oneHandedCtrlPanelStart?.setBackgroundColor(prefs.theme.oneHandedBgColor)
inputView?.oneHandedCtrlPanelEnd?.setBackgroundColor(prefs.theme.oneHandedBgColor)
inputView?.findViewById<ImageButton>(R.id.one_handed_ctrl_move_start)
?.imageTintList = ColorStateList.valueOf(prefs.theme.oneHandedButtonFgColor)
inputView?.findViewById<ImageButton>(R.id.one_handed_ctrl_move_end)
?.imageTintList = ColorStateList.valueOf(prefs.theme.oneHandedButtonFgColor)
inputView?.findViewById<ImageButton>(R.id.one_handed_ctrl_close_start)
?.imageTintList = ColorStateList.valueOf(prefs.theme.oneHandedButtonFgColor)
inputView?.findViewById<ImageButton>(R.id.one_handed_ctrl_close_end)
?.imageTintList = ColorStateList.valueOf(prefs.theme.oneHandedButtonFgColor)
eventListeners.toList().forEach { it.onApplyThemeAttributes() }
// Update status bar to be transparent
// Done as starting with Android 11 the IME Window takes the primaryColorDark value and
// colors the status bar, which isn't the desired behavior. (See issue #43)
flags = flags or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
w.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
w.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
w.statusBarColor = Color.TRANSPARENT
// Apply the new flags to the DecorView
w.decorView.systemUiVisibility = flags
// Update InputView theme
inputView?.setBackgroundColor(theme.getAttr(Theme.Attr.KEYBOARD_BACKGROUND).toSolidColor().color)
inputView?.oneHandedCtrlPanelStart?.setBackgroundColor(theme.getAttr(Theme.Attr.ONE_HANDED_BACKGROUND).toSolidColor().color)
inputView?.oneHandedCtrlPanelEnd?.setBackgroundColor(theme.getAttr(Theme.Attr.ONE_HANDED_BACKGROUND).toSolidColor().color)
ColorStateList.valueOf(theme.getAttr(Theme.Attr.ONE_HANDED_FOREGROUND).toSolidColor().color).also {
inputView?.oneHandedCtrlMoveStart?.imageTintList = it
inputView?.oneHandedCtrlMoveEnd?.imageTintList = it
inputView?.oneHandedCtrlCloseStart?.imageTintList = it
inputView?.oneHandedCtrlCloseEnd?.imageTintList = it
}
eventListeners.toList().forEach { it?.get()?.onApplyThemeAttributes() }
}
override fun onComputeInsets(outInsets: Insets?) {
@@ -407,6 +471,7 @@ class FlorisBoard : InputMethodService() {
SwipeAction.HIDE_KEYBOARD -> requestHideSelf(0)
SwipeAction.SWITCH_TO_PREV_SUBTYPE -> switchToPrevSubtype()
SwipeAction.SWITCH_TO_NEXT_SUBTYPE -> switchToNextSubtype()
SwipeAction.SWITCH_TO_PREV_KEYBOARD -> switchToPrevKeyboard()
else -> textInputManager.executeSwipeAction(swipeAction)
}
}
@@ -430,6 +495,21 @@ class FlorisBoard : InputMethodService() {
return subtypeManager.subtypes.size > 1
}
fun switchToPrevKeyboard(){
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
switchToPreviousInputMethod()
} else {
window.window?.let { window ->
imeManager?.switchToLastInputMethod(window.attributes.token)
}
}
} catch (e: Exception) {
Timber.e(e,"Unable to switch to the previous IME")
imeManager?.showInputMethodPicker()
}
}
fun switchToPrevSubtype() {
activeSubtype = subtypeManager.switchToPrevSubtype() ?: Subtype.DEFAULT
onSubtypeChanged(activeSubtype)
@@ -448,25 +528,21 @@ class FlorisBoard : InputMethodService() {
fun setActiveInput(type: Int) {
when (type) {
R.id.text_input -> {
inputView?.mainViewFlipper?.displayedChild =
inputView?.mainViewFlipper?.indexOfChild(textInputManager.textViewGroup) ?: 0
inputView?.mainViewFlipper?.displayedChild = 0
}
R.id.media_input -> {
inputView?.mainViewFlipper?.displayedChild =
inputView?.mainViewFlipper?.indexOfChild(mediaInputManager.mediaViewGroup) ?: 0
inputView?.mainViewFlipper?.displayedChild = 1
}
}
}
private fun initializeOneHandedEnvironment() {
inputView?.findViewById<ImageButton>(R.id.one_handed_ctrl_move_start)
?.setOnClickListener { v -> onOneHandedPanelButtonClick(v) }
inputView?.findViewById<ImageButton>(R.id.one_handed_ctrl_move_end)
?.setOnClickListener { v -> onOneHandedPanelButtonClick(v) }
inputView?.findViewById<ImageButton>(R.id.one_handed_ctrl_close_start)
?.setOnClickListener { v -> onOneHandedPanelButtonClick(v) }
inputView?.findViewById<ImageButton>(R.id.one_handed_ctrl_close_end)
?.setOnClickListener { v -> onOneHandedPanelButtonClick(v) }
{ v:View -> onOneHandedPanelButtonClick(v) }.also {
inputView?.oneHandedCtrlMoveStart?.setOnClickListener(it)
inputView?.oneHandedCtrlMoveEnd?.setOnClickListener(it)
inputView?.oneHandedCtrlCloseStart?.setOnClickListener(it)
inputView?.oneHandedCtrlCloseEnd?.setOnClickListener(it)
}
}
private fun onOneHandedPanelButtonClick(v: View) {
@@ -523,25 +599,36 @@ class FlorisBoard : InputMethodService() {
}, 0)
}
override fun onPrimaryClipChanged() {
eventListeners.toList().forEach { it?.get()?.onPrimaryClipChanged() }
}
/**
* Adds a given [listener] to the list which will receive FlorisBoard events.
*
* @param listener The listener object which receives the events.
* @returns True if the listener has been added successfully, false otherwise.
* @return True if the listener has been added successfully, false otherwise.
*/
fun addEventListener(listener: EventListener): Boolean {
return eventListeners.add(listener)
return eventListeners.add(WeakReference(listener))
}
/**
* Removes a given [listener] from the list which will receive FlorisBoard events.
*
* TODO: implement this function with a proper iterator
*
* @param listener The same listener object which was used in [addEventListener].
* @returns True if the listener has been removed successfully, false otherwise. A false return
* @return True if the listener has been removed successfully, false otherwise. A false return
* value may also indicate that the [listener] was not added previously.
*/
fun removeEventListener(listener: EventListener): Boolean {
return eventListeners.remove(listener)
eventListeners.toList().forEach {
if (it?.get() == listener) {
return eventListeners.remove(it)
}
}
return false
}
interface EventListener {
@@ -550,23 +637,16 @@ class FlorisBoard : InputMethodService() {
fun onRegisterInputView(inputView: InputView) {}
fun onDestroy() {}
fun onStartInputView(info: EditorInfo?, restarting: Boolean) {}
fun onStartInputView(instance: EditorInstance, restarting: Boolean) {}
fun onFinishInputView(finishingInput: Boolean) {}
fun onWindowShown() {}
fun onWindowHidden() {}
fun onUpdateCursorAnchorInfo(cursorAnchorInfo: CursorAnchorInfo?) {}
fun onUpdateSelection(
oldSelStart: Int,
oldSelEnd: Int,
newSelStart: Int,
newSelEnd: Int,
candidatesStart: Int,
candidatesEnd: Int
) {}
fun onUpdateSelection() {}
fun onApplyThemeAttributes() {}
fun onPrimaryClipChanged() {}
fun onSubtypeChanged(newSubtype: Subtype) {}
}

View File

@@ -19,12 +19,17 @@ package dev.patrickgold.florisboard.ime.core
import android.content.Context
import android.content.res.Configuration
import android.util.AttributeSet
import android.util.DisplayMetrics
import android.util.Log
import android.widget.ImageButton
import android.widget.LinearLayout
import android.widget.ViewFlipper
import dev.patrickgold.florisboard.BuildConfig
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.text.key.KeyVariation
import dev.patrickgold.florisboard.ime.text.keyboard.KeyboardMode
import dev.patrickgold.florisboard.util.ViewLayoutUtils
import timber.log.Timber
import kotlin.math.roundToInt
/**
@@ -34,13 +39,13 @@ class InputView : LinearLayout {
private var florisboard: FlorisBoard = FlorisBoard.getInstance()
private val prefs: PrefHelper = PrefHelper.getDefaultInstance(context)
var desiredInputViewHeight: Int = resources.getDimension(R.dimen.inputView_baseHeight).roundToInt()
var desiredInputViewHeight: Float = resources.getDimension(R.dimen.inputView_baseHeight)
private set
var desiredSmartbarHeight: Int = resources.getDimension(R.dimen.smartbar_baseHeight).roundToInt()
var desiredSmartbarHeight: Float = resources.getDimension(R.dimen.smartbar_baseHeight)
private set
var desiredTextKeyboardViewHeight: Int = resources.getDimension(R.dimen.textKeyboardView_baseHeight).roundToInt()
var desiredTextKeyboardViewHeight: Float = resources.getDimension(R.dimen.textKeyboardView_baseHeight)
private set
var desiredMediaKeyboardViewHeight: Int = resources.getDimension(R.dimen.mediaKeyboardView_baseHeight).roundToInt()
var desiredMediaKeyboardViewHeight: Float = resources.getDimension(R.dimen.mediaKeyboardView_baseHeight)
private set
var mainViewFlipper: ViewFlipper? = null
@@ -49,26 +54,42 @@ class InputView : LinearLayout {
private set
var oneHandedCtrlPanelEnd: LinearLayout? = null
private set
var oneHandedCtrlMoveStart: ImageButton? = null
private set
var oneHandedCtrlMoveEnd: ImageButton? = null
private set
var oneHandedCtrlCloseStart: ImageButton? = null
private set
var oneHandedCtrlCloseEnd: ImageButton? = null
private set
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
)
override fun onAttachedToWindow() {
if (BuildConfig.DEBUG) Log.i(this::class.simpleName, "onAttachedToWindow()")
Timber.i("onAttachedToWindow()")
super.onAttachedToWindow()
mainViewFlipper = findViewById(R.id.main_view_flipper)
oneHandedCtrlPanelStart = findViewById(R.id.one_handed_ctrl_panel_start)
oneHandedCtrlPanelEnd = findViewById(R.id.one_handed_ctrl_panel_end)
oneHandedCtrlMoveStart = findViewById(R.id.one_handed_ctrl_move_start)
oneHandedCtrlMoveEnd = findViewById(R.id.one_handed_ctrl_move_end)
oneHandedCtrlCloseStart = findViewById(R.id.one_handed_ctrl_close_start)
oneHandedCtrlCloseEnd = findViewById(R.id.one_handed_ctrl_close_end)
florisboard.registerInputView(this)
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val heightFactor = when (resources.configuration.orientation) {
Configuration.ORIENTATION_LANDSCAPE -> 0.85f
Configuration.ORIENTATION_LANDSCAPE -> 1.0f
else -> if (prefs.keyboard.oneHandedMode != "off") {
0.9f
} else {
@@ -82,17 +103,71 @@ class InputView : LinearLayout {
"mid_tall" -> 1.05f
"tall" -> 1.10f
"extra_tall" -> 1.15f
"custom" -> prefs.keyboard.heightFactorCustom.toFloat() / 100.0f
else -> 1.00f
}
var height = (resources.getDimension(R.dimen.inputView_baseHeight) * heightFactor).roundToInt()
desiredInputViewHeight = height
desiredSmartbarHeight = (0.16129 * height).roundToInt()
desiredTextKeyboardViewHeight = height - desiredSmartbarHeight
desiredMediaKeyboardViewHeight = height
var baseHeight = calcInputViewHeight() * heightFactor
var baseSmartbarHeight = 0.16129f * baseHeight
var baseTextInputHeight = baseHeight - baseSmartbarHeight
val tim = florisboard.textInputManager
val shouldGiveAdditionalSpace = prefs.keyboard.numberRow &&
!(tim.getActiveKeyboardMode() == KeyboardMode.NUMERIC ||
tim.getActiveKeyboardMode() == KeyboardMode.PHONE ||
tim.getActiveKeyboardMode() == KeyboardMode.PHONE2)
if (shouldGiveAdditionalSpace) {
val additionalHeight = desiredTextKeyboardViewHeight * 0.18f
baseHeight += additionalHeight
baseTextInputHeight += additionalHeight
}
val smartbarDisabled = !prefs.smartbar.enabled ||
tim.keyVariation == KeyVariation.PASSWORD && prefs.keyboard.numberRow ||
tim.getActiveKeyboardMode() == KeyboardMode.NUMERIC ||
tim.getActiveKeyboardMode() == KeyboardMode.PHONE ||
tim.getActiveKeyboardMode() == KeyboardMode.PHONE2
if (smartbarDisabled) {
baseHeight = baseTextInputHeight
baseSmartbarHeight = 0.0f
}
desiredInputViewHeight = baseHeight
desiredSmartbarHeight = baseSmartbarHeight
desiredTextKeyboardViewHeight = baseTextInputHeight
desiredMediaKeyboardViewHeight = baseHeight
// Add bottom offset for curved screens here. As the desired heights have already been set,
// adding a value to the height now will result in a bottom padding (aka offset).
height += ViewLayoutUtils.convertDpToPixel(florisboard.prefs.keyboard.bottomOffset.toFloat(), context).toInt()
baseHeight += ViewLayoutUtils.convertDpToPixel(
florisboard.prefs.keyboard.bottomOffset.toFloat(),
context
)
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY))
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(baseHeight.roundToInt(), MeasureSpec.EXACTLY))
}
/**
* Calculates the input view height based on the current screen dimensions and the auto
* selected dimension values.
*
* This method and the fraction values have been inspired by [OpenBoard](https://github.com/dslul/openboard)
* but are not 1:1 the same. This implementation differs from the
* [original](https://github.com/dslul/openboard/blob/90ae4c8aec034a8935e1fd02b441be25c7dba6ce/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/ResourceUtils.java)
* by calculating the average of the min and max height values, then taking at least the input
* view base height and return this resulting value.
*/
private fun calcInputViewHeight(): Float {
val dm: DisplayMetrics = resources.displayMetrics
val minBaseSize: Float = when (resources.configuration.orientation) {
Configuration.ORIENTATION_LANDSCAPE -> resources.getFraction(
R.fraction.inputView_minHeightFraction, dm.heightPixels, dm.heightPixels
)
else -> resources.getFraction(
R.fraction.inputView_minHeightFraction, dm.widthPixels, dm.widthPixels
)
}
val maxBaseSize: Float = resources.getFraction(
R.fraction.inputView_maxHeightFraction, dm.heightPixels, dm.heightPixels
)
return ((minBaseSize + maxBaseSize) / 2.0f).coerceAtLeast(
resources.getDimension(R.dimen.inputView_baseHeight)
)
}
}

View File

@@ -24,6 +24,9 @@ import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.text.gestures.DistanceThreshold
import dev.patrickgold.florisboard.ime.text.gestures.SwipeAction
import dev.patrickgold.florisboard.ime.text.gestures.VelocityThreshold
import dev.patrickgold.florisboard.ime.text.key.KeyHintMode
import dev.patrickgold.florisboard.ime.theme.ThemeMode
import dev.patrickgold.florisboard.util.TimeUtil
import dev.patrickgold.florisboard.util.VersionName
import kotlin.collections.HashMap
@@ -45,6 +48,7 @@ class PrefHelper(
val internal = Internal(this)
val keyboard = Keyboard(this)
val localization = Localization(this)
val smartbar = Smartbar(this)
val suggestion = Suggestion(this)
val theme = Theme(this)
@@ -137,6 +141,8 @@ class PrefHelper(
PreferenceManager.setDefaultValues(context, R.xml.prefs_keyboard, true)
PreferenceManager.setDefaultValues(context, R.xml.prefs_theme, true)
PreferenceManager.setDefaultValues(context, R.xml.prefs_typing, true)
//theme.dayThemeRef = "assets:ime/theme/floris_day.json"
//theme.nightThemeRef = "assets:ime/theme/floris_night.json"
//setPref(Keyboard.SUBTYPES, "")
//setPref(Internal.IS_IME_SET_UP, false)
}
@@ -165,6 +171,7 @@ class PrefHelper(
companion object {
const val SETTINGS_THEME = "advanced__settings_theme"
const val SHOW_APP_ICON = "advanced__show_app_icon"
const val FORCE_PRIVATE_MODE = "advanced__force_private_mode"
}
var settingsTheme: String = ""
@@ -173,6 +180,9 @@ class PrefHelper(
var showAppIcon: Boolean = false
get() = prefHelper.getPref(SHOW_APP_ICON, true)
private set
var forcePrivateMode: Boolean
get() = prefHelper.getPref(FORCE_PRIVATE_MODE, false)
set(v) = prefHelper.setPref(FORCE_PRIVATE_MODE, v)
}
/**
@@ -180,17 +190,20 @@ class PrefHelper(
*/
class Correction(private val prefHelper: PrefHelper) {
companion object {
const val AUTO_CAPITALIZATION = "correction__auto_capitalization"
const val DOUBLE_SPACE_PERIOD = "correction__double_space_period"
const val AUTO_CAPITALIZATION = "correction__auto_capitalization"
const val DOUBLE_SPACE_PERIOD = "correction__double_space_period"
const val REMEMBER_CAPS_LOCK_STATE = "correction__remember_caps_lock_state"
}
var autoCapitalization: Boolean = false
get() = prefHelper.getPref(AUTO_CAPITALIZATION, true)
private set
var doubleSpacePeriod: Boolean = false
get() = prefHelper.getPref(DOUBLE_SPACE_PERIOD, true)
private set
var autoCapitalization: Boolean
get() = prefHelper.getPref(AUTO_CAPITALIZATION, true)
set(v) = prefHelper.setPref(AUTO_CAPITALIZATION, v)
var doubleSpacePeriod: Boolean
get() = prefHelper.getPref(DOUBLE_SPACE_PERIOD, true)
set(v) = prefHelper.setPref(DOUBLE_SPACE_PERIOD, v)
var rememberCapsLockState: Boolean
get() = prefHelper.getPref(REMEMBER_CAPS_LOCK_STATE, false)
set(v) = prefHelper.setPref(REMEMBER_CAPS_LOCK_STATE, v)
}
/**
@@ -202,8 +215,10 @@ class PrefHelper(
const val SWIPE_DOWN = "gestures__swipe_down"
const val SWIPE_LEFT = "gestures__swipe_left"
const val SWIPE_RIGHT = "gestures__swipe_right"
const val SPACE_BAR_LONG_PRESS = "gestures__space_bar_long_press"
const val SPACE_BAR_SWIPE_LEFT = "gestures__space_bar_swipe_left"
const val SPACE_BAR_SWIPE_RIGHT = "gestures__space_bar_swipe_right"
const val SPACE_BAR_SWIPE_UP = "gestures__space_bar_swipe_up"
const val DELETE_KEY_SWIPE_LEFT = "gestures__delete_key_swipe_left"
const val SWIPE_VELOCITY_THRESHOLD = "gestures__swipe_velocity_threshold"
const val SWIPE_DISTANCE_THRESHOLD = "gestures__swipe_distance_threshold"
@@ -221,6 +236,12 @@ class PrefHelper(
var swipeRight: SwipeAction
get() = SwipeAction.fromString(prefHelper.getPref(SWIPE_RIGHT, "no_action"))
set(v) = prefHelper.setPref(SWIPE_RIGHT, v)
var spaceBarLongPress: SwipeAction
get() = SwipeAction.fromString(prefHelper.getPref(SPACE_BAR_LONG_PRESS, "no_action"))
set(v) = prefHelper.setPref(SPACE_BAR_LONG_PRESS, v)
var spaceBarSwipeUp: SwipeAction
get() = SwipeAction.fromString(prefHelper.getPref(SPACE_BAR_SWIPE_UP, "no_action"))
set(v) = prefHelper.setPref(SPACE_BAR_SWIPE_UP, v)
var spaceBarSwipeLeft: SwipeAction
get() = SwipeAction.fromString(prefHelper.getPref(SPACE_BAR_SWIPE_LEFT, "no_action"))
set(v) = prefHelper.setPref(SPACE_BAR_SWIPE_LEFT, v)
@@ -262,9 +283,6 @@ class PrefHelper(
class Internal(private val prefHelper: PrefHelper) {
companion object {
const val IS_IME_SET_UP = "internal__is_ime_set_up"
const val THEME_CURRENT_BASED_ON = "internal__theme_current_based_on"
const val THEME_CURRENT_IS_MODIFIED = "internal__theme_current_is_modified"
const val THEME_CURRENT_IS_NIGHT = "internal__theme_current_is_night"
const val VERSION_ON_INSTALL = "internal__version_on_install"
const val VERSION_LAST_USE = "internal__version_last_use"
const val VERSION_LAST_CHANGELOG = "internal__version_last_changelog"
@@ -273,15 +291,6 @@ class PrefHelper(
var isImeSetUp: Boolean
get() = prefHelper.getPref(IS_IME_SET_UP, false)
set(v) = prefHelper.setPref(IS_IME_SET_UP, v)
var themeCurrentBasedOn: String
get() = prefHelper.getPref(THEME_CURRENT_BASED_ON, "undefined")
set(v) = prefHelper.setPref(THEME_CURRENT_BASED_ON, v)
var themeCurrentIsModified: Boolean
get() = prefHelper.getPref(THEME_CURRENT_IS_MODIFIED, false)
set(v) = prefHelper.setPref(THEME_CURRENT_IS_MODIFIED, v)
var themeCurrentIsNight: Boolean
get() = prefHelper.getPref(THEME_CURRENT_IS_NIGHT, false)
set(v) = prefHelper.setPref(THEME_CURRENT_IS_NIGHT, v)
var versionOnInstall: String
get() = prefHelper.getPref(VERSION_ON_INSTALL, VersionName.DEFAULT_RAW)
set(v) = prefHelper.setPref(VERSION_ON_INSTALL, v)
@@ -298,34 +307,50 @@ class PrefHelper(
*/
class Keyboard(private val prefHelper: PrefHelper) {
companion object {
const val BOTTOM_OFFSET = "keyboard__bottom_offset"
const val HEIGHT_FACTOR = "keyboard__height_factor"
const val HINTED_NUMBER_ROW = "keyboard__hinted_number_row"
const val HINTED_SYMBOLS = "keyboard__hinted_symbols"
const val LONG_PRESS_DELAY = "keyboard__long_press_delay"
const val ONE_HANDED_MODE = "keyboard__one_handed_mode"
const val POPUP_ENABLED = "keyboard__popup_enabled"
const val SOUND_ENABLED = "keyboard__sound_enabled"
const val SOUND_VOLUME = "keyboard__sound_volume"
const val VIBRATION_ENABLED = "keyboard__vibration_enabled"
const val VIBRATION_STRENGTH = "keyboard__vibration_strength"
const val BOTTOM_OFFSET = "keyboard__bottom_offset"
const val FONT_SIZE_MULTIPLIER_PORTRAIT = "keyboard__font_size_multiplier_portrait"
const val FONT_SIZE_MULTIPLIER_LANDSCAPE = "keyboard__font_size_multiplier_landscape"
const val HEIGHT_FACTOR = "keyboard__height_factor"
const val HEIGHT_FACTOR_CUSTOM = "keyboard__height_factor_custom"
const val HINTED_NUMBER_ROW_MODE = "keyboard__hinted_number_row_mode"
const val HINTED_SYMBOLS_MODE = "keyboard__hinted_symbols_mode"
const val LONG_PRESS_DELAY = "keyboard__long_press_delay"
const val NUMBER_ROW = "keyboard__number_row"
const val ONE_HANDED_MODE = "keyboard__one_handed_mode"
const val POPUP_ENABLED = "keyboard__popup_enabled"
const val SOUND_ENABLED = "keyboard__sound_enabled"
const val SOUND_VOLUME = "keyboard__sound_volume"
const val VIBRATION_ENABLED = "keyboard__vibration_enabled"
const val VIBRATION_STRENGTH = "keyboard__vibration_strength"
}
var bottomOffset: Int = 0
get() = prefHelper.getPref(BOTTOM_OFFSET, 0)
private set
var fontSizeMultiplierPortrait: Int
get() = prefHelper.getPref(FONT_SIZE_MULTIPLIER_PORTRAIT, 100)
set(v) = prefHelper.setPref(FONT_SIZE_MULTIPLIER_PORTRAIT, v)
var fontSizeMultiplierLandscape: Int
get() = prefHelper.getPref(FONT_SIZE_MULTIPLIER_LANDSCAPE, 100)
set(v) = prefHelper.setPref(FONT_SIZE_MULTIPLIER_LANDSCAPE, v)
var heightFactor: String = ""
get() = prefHelper.getPref(HEIGHT_FACTOR, "normal")
private set
var hintedNumberRow: Boolean
get() = prefHelper.getPref(HINTED_NUMBER_ROW, true)
set(v) = prefHelper.setPref(HINTED_NUMBER_ROW, v)
var hintedSymbols: Boolean
get() = prefHelper.getPref(HINTED_SYMBOLS, true)
set(v) = prefHelper.setPref(HINTED_SYMBOLS, v)
var heightFactorCustom: Int
get() = prefHelper.getPref(HEIGHT_FACTOR_CUSTOM, 100)
set(v) = prefHelper.setPref(HEIGHT_FACTOR_CUSTOM, v)
var hintedNumberRowMode: KeyHintMode
get() = KeyHintMode.fromString(prefHelper.getPref(HINTED_NUMBER_ROW_MODE, KeyHintMode.ENABLED_ACCENT_PRIORITY.toString()))
set(v) = prefHelper.setPref(HINTED_NUMBER_ROW_MODE, v)
var hintedSymbolsMode: KeyHintMode
get() = KeyHintMode.fromString(prefHelper.getPref(HINTED_SYMBOLS_MODE, KeyHintMode.ENABLED_ACCENT_PRIORITY.toString()))
set(v) = prefHelper.setPref(HINTED_SYMBOLS_MODE, v)
var longPressDelay: Int = 0
get() = prefHelper.getPref(LONG_PRESS_DELAY, 300)
private set
var numberRow: Boolean
get() = prefHelper.getPref(NUMBER_ROW, false)
set(v) = prefHelper.setPref(NUMBER_ROW, v)
var oneHandedMode: String
get() = prefHelper.getPref(ONE_HANDED_MODE, "off")
set(value) = prefHelper.setPref(ONE_HANDED_MODE, value)
@@ -365,25 +390,38 @@ class PrefHelper(
set(v) = prefHelper.setPref(SUBTYPES, v)
}
/**
* Wrapper class for Smartbar preferences.
*/
class Smartbar(private val prefHelper: PrefHelper) {
companion object {
const val ENABLED = "smartbar__enabled"
}
var enabled: Boolean
get() = prefHelper.getPref(ENABLED, true)
set(v) = prefHelper.setPref(ENABLED, v)
}
/**
* Wrapper class for suggestion preferences.
*/
class Suggestion(private val prefHelper: PrefHelper) {
companion object {
const val ENABLED = "suggestion__enabled"
const val SHOW_INSTEAD = "suggestion__show_instead"
const val USE_PREV_WORDS = "suggestion__use_prev_words"
const val ENABLED = "suggestion__enabled"
const val SUGGEST_CLIPBOARD_CONTENT = "suggestion__suggest_clipboard_content"
const val USE_PREV_WORDS = "suggestion__use_prev_words"
}
var enabled: Boolean = false
get() = prefHelper.getPref(ENABLED, true)
private set
var showInstead: String = ""
get() = prefHelper.getPref(SHOW_INSTEAD, "number_row")
private set
var usePrevWords: Boolean = false
get() = prefHelper.getPref(USE_PREV_WORDS, true)
private set
var enabled: Boolean
get() = prefHelper.getPref(ENABLED, true)
set(v) = prefHelper.setPref(ENABLED, v)
var suggestClipboardContent: Boolean
get() = prefHelper.getPref(SUGGEST_CLIPBOARD_CONTENT, false)
set(v) = prefHelper.setPref(SUGGEST_CLIPBOARD_CONTENT, v)
var usePrevWords: Boolean
get() = prefHelper.getPref(USE_PREV_WORDS, true)
set(v) = prefHelper.setPref(USE_PREV_WORDS, v)
}
/**
@@ -391,119 +429,35 @@ class PrefHelper(
*/
class Theme(private val prefHelper: PrefHelper) {
companion object {
const val COLOR_PRIMARY = "theme__colorPrimary"
const val COLOR_PRIMARY_DARK = "theme__colorPrimaryDark"
const val COLOR_ACCENT = "theme__colorAccent"
const val NAV_BAR_COLOR = "theme__navBarColor"
const val NAV_BAR_IS_LIGHT = "theme__navBarIsLight"
const val KEYBOARD_BG_COLOR = "theme__keyboard_bgColor"
const val KEY_BG_COLOR = "theme__key_bgColor"
const val KEY_BG_COLOR_PRESSED = "theme__key_bgColorPressed"
const val KEY_FG_COLOR = "theme__key_fgColor"
const val KEY_ENTER_BG_COLOR = "theme__keyEnter_bgColor"
const val KEY_ENTER_BG_COLOR_PRESSED = "theme__keyEnter_bgColorPressed"
const val KEY_ENTER_FG_COLOR = "theme__keyEnter_fgColor"
const val KEY_SHIFT_BG_COLOR = "theme__keyShift_bgColor"
const val KEY_SHIFT_BG_COLOR_PRESSED = "theme__keyShift_bgColorPressed"
const val KEY_SHIFT_FG_COLOR = "theme__keyShift_fgColor"
const val KEY_SHIFT_FG_COLOR_CAPSLOCK = "theme__keyShift_fgColorCapsLock"
const val KEY_POPUP_BG_COLOR = "theme__keyPopup_bgColor"
const val KEY_POPUP_BG_COLOR_ACTIVE = "theme__keyPopup_bgColorActive"
const val KEY_POPUP_FG_COLOR = "theme__keyPopup_fgColor"
const val MEDIA_FG_COLOR = "theme__media_fgColor"
const val MEDIA_FG_COLOR_ALT = "theme__media_fgColorAlt"
const val ONE_HANDED_BG_COLOR = "theme__oneHanded_bgColor"
const val ONE_HANDED_BUTTON_FG_COLOR = "theme__oneHandedButton_fgColor"
const val SMARTBAR_BG_COLOR = "theme__smartbar_bgColor"
const val SMARTBAR_FG_COLOR = "theme__smartbar_fgColor"
const val SMARTBAR_FG_COLOR_ALT = "theme__smartbar_fgColorAlt"
const val SMARTBAR_BUTTON_BG_COLOR = "theme__smartbarButton_bgColor"
const val SMARTBAR_BUTTON_FG_COLOR = "theme__smartbarButton_fgColor"
const val MODE = "theme__mode"
const val DAY_THEME_REF = "theme__day_theme_ref"
const val DAY_THEME_ADAPT_TO_APP = "theme__day_theme_adapt_to_app"
const val NIGHT_THEME_REF = "theme__night_theme_ref"
const val NIGHT_THEME_ADAPT_TO_APP = "theme__night_theme_adapt_to_app"
const val SUNRISE_TIME = "theme__sunrise_time"
const val SUNSET_TIME = "theme__sunset_time"
}
var colorPrimary: Int
get() = prefHelper.getPref(COLOR_PRIMARY, 0)
set(v) = prefHelper.setPref(COLOR_PRIMARY, v)
var colorPrimaryDark: Int
get() = prefHelper.getPref(COLOR_PRIMARY_DARK, 0)
set(v) = prefHelper.setPref(COLOR_PRIMARY_DARK, v)
var colorAccent: Int
get() = prefHelper.getPref(COLOR_ACCENT, 0)
set(v) = prefHelper.setPref(COLOR_ACCENT, v)
var navBarColor: Int
get() = prefHelper.getPref(NAV_BAR_COLOR, 0)
set(v) = prefHelper.setPref(NAV_BAR_COLOR, v)
var navBarIsLight: Boolean
get() = prefHelper.getPref(NAV_BAR_IS_LIGHT, false)
set(v) = prefHelper.setPref(NAV_BAR_IS_LIGHT, v)
var keyboardBgColor: Int
get() = prefHelper.getPref(KEYBOARD_BG_COLOR, 0)
set(v) = prefHelper.setPref(KEYBOARD_BG_COLOR, v)
var keyBgColor: Int
get() = prefHelper.getPref(KEY_BG_COLOR, 0)
set(v) = prefHelper.setPref(KEY_BG_COLOR, v)
var keyBgColorPressed: Int
get() = prefHelper.getPref(KEY_BG_COLOR_PRESSED, 0)
set(v) = prefHelper.setPref(KEY_BG_COLOR_PRESSED, v)
var keyFgColor: Int
get() = prefHelper.getPref(KEY_FG_COLOR, 0)
set(v) = prefHelper.setPref(KEY_FG_COLOR, v)
var keyEnterBgColor: Int
get() = prefHelper.getPref(KEY_ENTER_BG_COLOR, 0)
set(v) = prefHelper.setPref(KEY_ENTER_BG_COLOR, v)
var keyEnterBgColorPressed: Int
get() = prefHelper.getPref(KEY_ENTER_BG_COLOR_PRESSED, 0)
set(v) = prefHelper.setPref(KEY_ENTER_BG_COLOR_PRESSED, v)
var keyEnterFgColor: Int
get() = prefHelper.getPref(KEY_ENTER_FG_COLOR, 0)
set(v) = prefHelper.setPref(KEY_ENTER_FG_COLOR, v)
var keyShiftBgColor: Int
get() = prefHelper.getPref(KEY_SHIFT_BG_COLOR, 0)
set(v) = prefHelper.setPref(KEY_SHIFT_BG_COLOR, v)
var keyShiftBgColorPressed: Int
get() = prefHelper.getPref(KEY_SHIFT_BG_COLOR_PRESSED, 0)
set(v) = prefHelper.setPref(KEY_SHIFT_BG_COLOR_PRESSED, v)
var keyShiftFgColor: Int
get() = prefHelper.getPref(KEY_SHIFT_FG_COLOR, 0)
set(v) = prefHelper.setPref(KEY_SHIFT_FG_COLOR, v)
var keyShiftFgColorCapsLock: Int
get() = prefHelper.getPref(KEY_SHIFT_FG_COLOR_CAPSLOCK, 0)
set(v) = prefHelper.setPref(KEY_SHIFT_FG_COLOR_CAPSLOCK, v)
var keyPopupBgColor: Int
get() = prefHelper.getPref(KEY_POPUP_BG_COLOR, 0)
set(v) = prefHelper.setPref(KEY_POPUP_BG_COLOR, v)
var keyPopupBgColorActive: Int
get() = prefHelper.getPref(KEY_POPUP_BG_COLOR_ACTIVE, 0)
set(v) = prefHelper.setPref(KEY_POPUP_BG_COLOR_ACTIVE, v)
var keyPopupFgColor: Int
get() = prefHelper.getPref(KEY_POPUP_FG_COLOR, 0)
set(v) = prefHelper.setPref(KEY_POPUP_FG_COLOR, v)
var mediaFgColor: Int
get() = prefHelper.getPref(MEDIA_FG_COLOR, 0)
set(v) = prefHelper.setPref(MEDIA_FG_COLOR, v)
var mediaFgColorAlt: Int
get() = prefHelper.getPref(MEDIA_FG_COLOR_ALT, 0)
set(v) = prefHelper.setPref(MEDIA_FG_COLOR_ALT, v)
var oneHandedBgColor: Int
get() = prefHelper.getPref(ONE_HANDED_BG_COLOR, 0)
set(v) = prefHelper.setPref(ONE_HANDED_BG_COLOR, v)
var oneHandedButtonFgColor: Int
get() = prefHelper.getPref(ONE_HANDED_BUTTON_FG_COLOR, 0)
set(v) = prefHelper.setPref(ONE_HANDED_BUTTON_FG_COLOR, v)
var smartbarBgColor: Int
get() = prefHelper.getPref(SMARTBAR_BG_COLOR, 0)
set(v) = prefHelper.setPref(SMARTBAR_BG_COLOR, v)
var smartbarFgColor: Int
get() = prefHelper.getPref(SMARTBAR_FG_COLOR, 0)
set(v) = prefHelper.setPref(SMARTBAR_FG_COLOR, v)
var smartbarFgColorAlt: Int
get() = prefHelper.getPref(SMARTBAR_FG_COLOR_ALT, 0)
set(v) = prefHelper.setPref(SMARTBAR_FG_COLOR_ALT, v)
var smartbarButtonBgColor: Int
get() = prefHelper.getPref(SMARTBAR_BUTTON_BG_COLOR, 0)
set(v) = prefHelper.setPref(SMARTBAR_BUTTON_BG_COLOR, v)
var smartbarButtonFgColor: Int
get() = prefHelper.getPref(SMARTBAR_BUTTON_FG_COLOR, 0)
set(v) = prefHelper.setPref(SMARTBAR_BUTTON_FG_COLOR, v)
var mode: ThemeMode
get() = ThemeMode.fromString(prefHelper.getPref(MODE, ThemeMode.FOLLOW_SYSTEM.toString()))
set(v) = prefHelper.setPref(MODE, v)
var dayThemeRef: String
get() = prefHelper.getPref(DAY_THEME_REF, "assets:ime/theme/floris_day.json")
set(v) = prefHelper.setPref(DAY_THEME_REF, v)
var dayThemeAdaptToApp: Boolean
get() = prefHelper.getPref(DAY_THEME_ADAPT_TO_APP, false)
set(v) = prefHelper.setPref(DAY_THEME_ADAPT_TO_APP, v)
var nightThemeRef: String
get() = prefHelper.getPref(NIGHT_THEME_REF, "assets:ime/theme/floris_night.json")
set(v) = prefHelper.setPref(NIGHT_THEME_REF, v)
var nightThemeAdaptToApp: Boolean
get() = prefHelper.getPref(NIGHT_THEME_ADAPT_TO_APP, false)
set(v) = prefHelper.setPref(NIGHT_THEME_ADAPT_TO_APP, v)
var sunriseTime: Int
get() = prefHelper.getPref(SUNRISE_TIME, TimeUtil.encode(6, 0))
set(v) = prefHelper.setPref(SUNRISE_TIME, v)
var sunsetTime: Int
get() = prefHelper.getPref(SUNSET_TIME, TimeUtil.encode(18, 0))
set(v) = prefHelper.setPref(SUNSET_TIME, v)
}
}

View File

@@ -87,16 +87,10 @@ data class Subtype(
* Must be a string which also exists in [FlorisBoard.ImeConfig.characterLayouts]. If the value is
* not included within this list, no layout will be shown to the user if the user selects the
* predefined layout value.
* @property isAsciiCapable Legacy attribute for Android's InputMethodSubtype. Currently no real
* use within this project.
* @property isEmojiCapable Legacy attribute for Android's InputMethodSubtype. Currently no real
* use within this project.
*/
data class DefaultSubtype(
var id: Int,
@Json(name = "languageTag")
var locale: Locale,
var preferredLayout: String,
var isAsciiCapable: Boolean,
var isEmojiCapable: Boolean
var preferredLayout: String
)

View File

@@ -71,7 +71,7 @@ class SubtypeManager(
* Loads the [FlorisBoard.ImeConfig] from ime/config.json.
*
* @param path The path to to IME config file.
* @returns The [FlorisBoard.ImeConfig] or a default config.
* @return The [FlorisBoard.ImeConfig] or a default config.
*/
private fun loadImeConfig(path: String): FlorisBoard.ImeConfig {
val rawJsonData: String = try {
@@ -93,7 +93,7 @@ class SubtypeManager(
* Adds a given [subtypeToAdd] to the subtype list, if it does not exist.
*
* @param subtypeToAdd The subtype which should be added.
* @returns True if the subtype was added, false otherwise. A return value of false indicates
* @return True if the subtype was added, false otherwise. A return value of false indicates
* that the subtype already exists.
*/
private fun addSubtype(subtypeToAdd: Subtype): Boolean {
@@ -112,7 +112,7 @@ class SubtypeManager(
*
* @param locale The locale of the subtype to be added.
* @param layoutName The layout name of the subtype to be added.
* @returns True if the subtype was added, false otherwise. A return value of false indicates
* @return True if the subtype was added, false otherwise. A return value of false indicates
* that the subtype already exists.
*/
fun addSubtype(locale: Locale, layoutName: String): Boolean {
@@ -129,7 +129,7 @@ class SubtypeManager(
* Gets the active subtype and returns it. If the activeSubtypeId points to a non-existent
* subtype, this method tries to determine a new active subtype.
*
* @returns The active subtype or null, if the subtype list is empty or no new active subtype
* @return The active subtype or null, if the subtype list is empty or no new active subtype
* could be determined.
*/
fun getActiveSubtype(): Subtype? {
@@ -152,7 +152,7 @@ class SubtypeManager(
* Gets a subtype by the given [id].
*
* @param id The id of the subtype you want to get.
* @returns The subtype or null, if no matching subtype could be found.
* @return The subtype or null, if no matching subtype could be found.
*/
fun getSubtypeById(id: Int): Subtype? {
for (subtype in subtypes) {
@@ -167,7 +167,7 @@ class SubtypeManager(
* Gets the default system subtype for a given [locale].
*
* @param locale The locale of the default system subtype to get.
* @returns The default system locale or null, if no matching default system subtype could be
* @return The default system locale or null, if no matching default system subtype could be
* found.
*/
fun getDefaultSubtypeForLocale(locale: Locale): DefaultSubtype? {
@@ -220,7 +220,7 @@ class SubtypeManager(
/**
* Switch to the previous subtype in the subtype list if possible.
*
* @returns The new active subtype or null if the determination process failed.
* @return The new active subtype or null if the determination process failed.
*/
fun switchToPrevSubtype(): Subtype? {
val subtypeList = subtypes
@@ -248,7 +248,7 @@ class SubtypeManager(
/**
* Switch to the next subtype in the subtype list if possible.
*
* @returns The new active subtype or null if the determination process failed.
* @return The new active subtype or null if the determination process failed.
*/
fun switchToNextSubtype(): Subtype? {
val subtypeList = subtypes

View File

@@ -0,0 +1,66 @@
/*
* Copyright (C) 2020 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.ime.extension
import android.content.Context
import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Result
/**
* Interface for an Asset to use within FlorisBoard. An asset is everything from a dictionary to a
* keyboard layout to a extended popup mapping, etc. Assets are very important for the splitting
* FlorisBoard's resources into assets.
*
* NOTE: At the current state, this is only a simple implementation idea and only PopupMappingAsset
* partly uses it. This package and it's classes are expected to grow and gain more importance over
* time.
*/
interface Asset {
/**
* The name of the Asset, must be unique throughout all Assets. Is used to internally identify
* and sort the Asset. This name is non-translatable and thus is a static string.
*/
val name: String
/**
* The display name of the Asset. This is the label which will be shown to the user in the
* Settings UI. Currently also a static string.
* TODO: make this string localize-able
*/
val label: String
/**
* A list of authors who actively worked on the content of this Asset. Any content of string is
* valid, but the best practice is to use the GitHub username.
*/
val authors: List<String>
/**
* "Static" functions which every Asset should provide.
*/
interface Companion<T> {
/**
* Creates an empty Asset of type [T].
*/
fun empty(): T
/**
* Loads an Asset of type [T] from the specified path.
*/
fun fromFile(context: Context, path: String): Result<T, Throwable> = Err(NotImplementedError())
}
}

View File

@@ -0,0 +1,233 @@
/*
* Copyright (C) 2020 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.ime.extension
import android.content.Context
import com.github.michaelbull.result.*
import com.squareup.moshi.Moshi
import com.squareup.moshi.adapters.PolymorphicJsonAdapterFactory
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import dev.patrickgold.florisboard.ime.popup.PopupExtension
import dev.patrickgold.florisboard.ime.text.key.KeyTypeAdapter
import dev.patrickgold.florisboard.ime.text.key.KeyVariationAdapter
import dev.patrickgold.florisboard.ime.text.layout.LayoutTypeAdapter
import dev.patrickgold.florisboard.ime.theme.Theme
import timber.log.Timber
import java.io.File
class AssetManager private constructor(private val applicationContext: Context) {
private val moshi: Moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
/*.add(PolymorphicJsonAdapterFactory.of(Asset::class.java, "\$type")
.withSubtype(PopupExtension::class.java, PopupExtension::class.qualifiedName)
.withSubtype(Theme::class.java, Theme::class.qualifiedName)
)*/
.add(LayoutTypeAdapter())
.add(KeyTypeAdapter())
.add(KeyVariationAdapter())
.build()
companion object {
private var defaultInstance: AssetManager? = null
fun init(applicationContext: Context): AssetManager {
val instance = AssetManager(applicationContext)
defaultInstance = instance
return instance
}
fun default(): AssetManager {
val instance = defaultInstance
if (instance != null) {
return instance
} else {
throw UninitializedPropertyAccessException(
"${this::class.simpleName} has not been initialized previously. Make sure to call init(applicationContext) before using default()."
)
}
}
}
fun deleteAsset(ref: AssetRef): Result<Nothing?, Throwable> {
return when (ref.source) {
AssetSource.Internal -> {
val file = File(applicationContext.filesDir.absolutePath + "/" + ref.path)
if (file.isFile) {
val success = file.delete()
if (success) {
Ok(null)
} else {
Err(Exception("Could not delete file."))
}
} else {
Err(Exception("Provided reference is not a file."))
}
}
else -> Err(Exception("Can not delete an asset in source '${ref.source}'"))
}
}
fun hasAsset(ref: AssetRef): Boolean {
return when (ref.source) {
AssetSource.Assets -> {
try {
val file = File(ref.path)
val list = applicationContext.assets.list(file.parent?.toString() ?: "")
list?.contains(file.name) == true
} catch (e: Exception) {
false
}
}
AssetSource.Internal -> {
val file = File(applicationContext.filesDir.absolutePath + "/" + ref.path)
file.exists() && file.isFile
}
else -> false
}
}
fun <T: Asset> listAssets(ref: AssetRef, assetClass: Class<T>): Result<Map<AssetRef, T>, Throwable> {
val retMap = mutableMapOf<AssetRef, T>()
return when (ref.source) {
AssetSource.Assets -> {
try {
val list = applicationContext.assets.list(ref.path)
if (list != null) {
for (file in list) {
val fileRef = ref.copy(path = ref.path + "/" + file)
val assetResult = loadAsset(fileRef, assetClass)
assetResult.onSuccess { asset ->
retMap[fileRef.copy()] = asset
}.onFailure { error ->
Timber.e(error.toString())
}
}
}
Ok(retMap.toMap())
} catch (e: Exception) {
Err(e)
}
}
AssetSource.Internal -> {
val dir = File(applicationContext.filesDir.absolutePath + "/" + ref.path)
if (dir.isDirectory) {
dir.listFiles()?.let {
it.forEach { file ->
if (file.isFile) {
val fileRef = ref.copy(path = ref.path + "/" + file.name)
val assetResult = loadAsset(fileRef, assetClass)
assetResult.onSuccess { asset ->
retMap[fileRef.copy()] = asset
}.onFailure { error ->
Timber.e(error.toString())
}
}
}
}
}
Ok(retMap.toMap())
}
else -> Ok(retMap.toMap())
}
}
fun <T: Asset> loadAsset(ref: AssetRef, assetClass: Class<T>): Result<T, Throwable> {
val rawJsonData = when (ref.source) {
is AssetSource.Assets -> {
try {
applicationContext.assets.open(ref.path).bufferedReader().use { it.readText() }
} catch (e: Exception) {
return Err(e)
}
}
is AssetSource.Internal -> {
val file = File(applicationContext.filesDir.absolutePath + "/" + ref.path)
val contents = readFile(file)
if (contents.isBlank()) {
"{}"
} else {
contents
}
}
else -> "{}"
}
return try {
val adapter = moshi.adapter(assetClass)
val asset = adapter.fromJson(rawJsonData)
if (asset != null) {
Ok(asset)
} else {
Err(NullPointerException("Asset failed to load!"))
}
} catch (e: Exception) {
Err(e)
}
}
fun <T: Asset> writeAsset(ref: AssetRef, assetClass: Class<T>, asset: T): Result<Boolean, Throwable> {
return when (ref.source) {
AssetSource.Internal -> {
val adapter = moshi.adapter(assetClass)
val rawJson = adapter.toJson(asset)
val file = File(applicationContext.filesDir.absolutePath + "/" + ref.path)
writeToFile(file, rawJson)
Ok(true)
}
else -> Err(Exception("Can not write an asset in source '${ref.source}'"))
}
}
/**
* Reads a given [file] and returns its content.
*
* @param file The file object.
* @return The contents of the file or an empty string, if the file does not exist.
*/
private fun readFile(file: File): String {
val retText = StringBuilder()
if (file.exists()) {
val newLine = System.lineSeparator()
file.forEachLine {
retText.append(it)
retText.append(newLine)
}
}
return retText.toString()
}
/**
* Writes given [text] to given [file]. If the file already exists, its current content
* will be overwritten.
*
* @param file The file object.
* @param text The text to write to the file.
* @return The contents of the file or an empty string, if the file does not exist.
*/
private fun writeToFile(file: File, text: String) {
try {
file.parent?.let {
val dir = File(it)
if (!dir.exists()) {
dir.mkdirs()
}
}
file.writeText(text)
} catch (e: Exception) {
e.printStackTrace()
}
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright (C) 2020 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.ime.extension
import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
import com.github.michaelbull.result.getOrElse
/**
* Data class which is a reference to an asset file. It indicates in which storage medium the asset
* is as well as the relative path to it.
*
* @property source The source in which the asset is (APK assets, internal storage, external)
* @property path The relative path to the asset within [source]. Must not begin and end with a
* forward slash.
*/
data class AssetRef(
val source: AssetSource,
val path: String
) {
companion object {
private const val DELIMITER: String = ":"
fun fromString(str: String): Result<AssetRef, String> {
val items = str.split(DELIMITER)
if (items.size != 2) {
return Err("Unexpected length of given asset ref. Make sure that the asset ref string contains exactly 2 items separated by '$DELIMITER'!")
}
val retSource = AssetSource.fromString(items[0]).getOrElse {
return Err(it)
}
return Ok(AssetRef(retSource, items[1]))
}
}
override fun toString(): String {
val retString: StringBuilder = StringBuilder().apply {
append(source.toString())
append(DELIMITER)
append(path)
}
return retString.toString()
}
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright (C) 2020 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.ime.extension
import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
import java.util.*
/**
* Sealed class which specifies where an asset comes from. There are 3 different types, all of which
* require a different approach on how to access the actual asset.
*/
sealed class AssetSource {
/**
* The asset comes pre-built with the application, thus all paths must be relative to the asset
* directory of FlorisBoard.
*/
object Assets : AssetSource()
/**
* The asset is saved in the internal storage of FlorisBoard, all relative paths must therefore
* be treated as such.
*/
object Internal : AssetSource()
/**
* Asset source is an external extension, which requires the package name and possibly other
* data. Currently NYI.
* TODO: Implement external extensions
*/
data class External(val packageName: String) : AssetSource() {
override fun toString(): String {
return super.toString()
}
}
companion object {
private val externalRegex: Regex = """^external\\(([a-z]+\\.)*[a-z]+\\)\$""".toRegex()
fun fromString(str: String): Result<AssetSource, String> {
return when (val string = str.toLowerCase(Locale.ENGLISH)) {
"assets" -> Ok(Assets)
"internal" -> Ok(Internal)
else -> {
if (string.matches(externalRegex)) {
val packageName = string.substring(9, string.length - 1)
Ok(External(packageName))
} else {
Err("'$str' is not a valid AssetSource.")
}
}
}
}
}
override fun toString(): String {
return when (this) {
is Assets -> "assets"
is Internal -> "internal"
is External -> "external($packageName)"
}
}
}

View File

@@ -17,13 +17,13 @@
package dev.patrickgold.florisboard.ime.media
import android.annotation.SuppressLint
import android.util.Log
import android.os.Handler
import android.view.MotionEvent
import android.view.View
import android.widget.*
import com.google.android.material.tabs.TabLayout
import dev.patrickgold.florisboard.BuildConfig
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.core.EditorInstance
import dev.patrickgold.florisboard.ime.core.FlorisBoard
import dev.patrickgold.florisboard.ime.core.InputView
import dev.patrickgold.florisboard.ime.media.emoji.EmojiKeyData
@@ -33,7 +33,10 @@ import dev.patrickgold.florisboard.ime.media.emoticon.EmoticonKeyboardView
import dev.patrickgold.florisboard.ime.text.key.KeyCode
import dev.patrickgold.florisboard.ime.text.key.KeyData
import dev.patrickgold.florisboard.ime.text.key.KeyType
import dev.patrickgold.florisboard.util.cancelAll
import dev.patrickgold.florisboard.util.postAtScheduledRate
import kotlinx.coroutines.*
import timber.log.Timber
import java.util.*
/**
@@ -50,10 +53,12 @@ class MediaInputManager private constructor() : CoroutineScope by MainScope(),
FlorisBoard.EventListener {
private val florisboard = FlorisBoard.getInstance()
private val activeEditorInstance: EditorInstance
get() = florisboard.activeEditorInstance
private var activeTab: Tab? = null
private var mediaViewFlipper: ViewFlipper? = null
private var osTimer: Timer? = null
private var repeatedKeyPressHandler: Handler? = null
private var tabLayout: TabLayout? = null
private val tabViews = EnumMap<Tab, LinearLayout>(Tab::class.java)
@@ -75,6 +80,11 @@ class MediaInputManager private constructor() : CoroutineScope by MainScope(),
florisboard.addEventListener(this)
}
override fun onCreateInputView() {
super.onCreateInputView()
repeatedKeyPressHandler = Handler(florisboard.context.mainLooper)
}
/**
* Called when a new input view has been registered. Used to initialize all media-relevant
* views and layouts.
@@ -82,7 +92,7 @@ class MediaInputManager private constructor() : CoroutineScope by MainScope(),
*/
@SuppressLint("ClickableViewAccessibility")
override fun onRegisterInputView(inputView: InputView) {
if (BuildConfig.DEBUG) Log.i(this::class.simpleName, "onRegisterInputView(inputView)")
Timber.i("onRegisterInputView(inputView)")
launch(Dispatchers.Default) {
mediaViewGroup = inputView.findViewById(R.id.media_input)
@@ -108,15 +118,12 @@ class MediaInputManager private constructor() : CoroutineScope by MainScope(),
override fun onTabReselected(tab: TabLayout.Tab) {}
})
for (tab in Tab.values()) {
val tabView = createTabViewFor(tab)
tabViews[tab] = tabView
withContext(Dispatchers.Main) {
withContext(Dispatchers.Main) {
for (tab in Tab.values()) {
val tabView = createTabViewFor(tab)
tabViews[tab] = tabView
mediaViewFlipper?.addView(tabView)
}
}
withContext(Dispatchers.Main) {
tabLayout?.selectTab(tabLayout?.getTabAt(0))
}
}
@@ -126,7 +133,7 @@ class MediaInputManager private constructor() : CoroutineScope by MainScope(),
* Clean-up of resources and stopping all coroutines.
*/
override fun onDestroy() {
if (BuildConfig.DEBUG) Log.i(this::class.simpleName, "onDestroy()")
Timber.i("onDestroy()")
cancel()
instance = null
@@ -139,10 +146,10 @@ class MediaInputManager private constructor() : CoroutineScope by MainScope(),
event ?: return false
val data = when (view.id) {
R.id.media_input_switch_to_text_input_button -> {
KeyData(KeyCode.SWITCH_TO_TEXT_CONTEXT)
KeyData(code = KeyCode.SWITCH_TO_TEXT_CONTEXT)
}
R.id.media_input_backspace_button -> {
KeyData(KeyCode.DELETE, type = KeyType.ENTER_EDITING)
KeyData(code = KeyCode.DELETE, type = KeyType.ENTER_EDITING)
}
else -> null
}
@@ -151,17 +158,14 @@ class MediaInputManager private constructor() : CoroutineScope by MainScope(),
florisboard.keyPressVibrate()
florisboard.keyPressSound(data)
if (data?.code == KeyCode.DELETE && data.type == KeyType.ENTER_EDITING) {
osTimer = Timer()
osTimer?.scheduleAtFixedRate(object : TimerTask() {
override fun run() {
florisboard.textInputManager.sendKeyPress(data)
}
}, 500, 50)
val delayMillis = florisboard.prefs.keyboard.longPressDelay.toLong()
repeatedKeyPressHandler?.postAtScheduledRate(delayMillis, 25) {
florisboard.textInputManager.sendKeyPress(data)
}
}
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
osTimer?.cancel()
osTimer = null
repeatedKeyPressHandler?.cancelAll()
if (event.actionMasked != MotionEvent.ACTION_CANCEL && data != null) {
florisboard.textInputManager.sendKeyPress(data)
}
@@ -199,18 +203,14 @@ class MediaInputManager private constructor() : CoroutineScope by MainScope(),
* Sends a given [emojiKeyData] to the current input editor.
*/
fun sendEmojiKeyPress(emojiKeyData: EmojiKeyData) {
val ic = florisboard.currentInputConnection
ic?.finishComposingText()
ic?.commitText(emojiKeyData.getCodePointsAsString(), 1)
activeEditorInstance.commitText(emojiKeyData.getCodePointsAsString())
}
/**
* Sends a given [emoticonKeyData] to the current input editor.
*/
fun sendEmoticonKeyPress(emoticonKeyData: EmoticonKeyData) {
val ic = florisboard.currentInputConnection
ic?.finishComposingText()
ic?.commitText(emoticonKeyData.icon, 1)
activeEditorInstance.commitText(emoticonKeyData.icon)
}
/**

View File

@@ -25,11 +25,14 @@ import android.widget.LinearLayout
import com.google.android.material.tabs.TabLayout
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.core.FlorisBoard
import dev.patrickgold.florisboard.ime.core.PrefHelper
import dev.patrickgold.florisboard.ime.theme.Theme
import dev.patrickgold.florisboard.ime.theme.ThemeManager
import kotlin.math.roundToInt
class MediaInputView : LinearLayout, FlorisBoard.EventListener {
class MediaInputView : LinearLayout, FlorisBoard.EventListener,
ThemeManager.OnThemeUpdatedListener {
private val florisboard: FlorisBoard? = FlorisBoard.getInstanceOrNull()
private val prefs: PrefHelper = PrefHelper.getDefaultInstance(context)
private val themeManager: ThemeManager = ThemeManager.default()
var tabLayout: TabLayout? = null
private set
@@ -46,22 +49,30 @@ class MediaInputView : LinearLayout, FlorisBoard.EventListener {
override fun onAttachedToWindow() {
super.onAttachedToWindow()
themeManager.registerOnThemeUpdatedListener(this)
tabLayout = findViewById(R.id.media_input_tabs)
switchToTextInputButton = findViewById(R.id.media_input_switch_to_text_input_button)
backspaceButton = findViewById(R.id.media_input_backspace_button)
onApplyThemeAttributes()
}
override fun onApplyThemeAttributes() {
tabLayout?.setTabTextColors(prefs.theme.mediaFgColor, prefs.theme.mediaFgColor)
tabLayout?.tabIconTint = ColorStateList.valueOf(prefs.theme.mediaFgColor)
tabLayout?.setSelectedTabIndicatorColor(prefs.theme.colorPrimary)
switchToTextInputButton?.setTextColor(prefs.theme.mediaFgColor)
backspaceButton?.imageTintList = ColorStateList.valueOf(prefs.theme.mediaFgColor)
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
themeManager.unregisterOnThemeUpdatedListener(this)
}
override fun onThemeUpdated(theme: Theme) {
val fgColor = theme.getAttr(Theme.Attr.MEDIA_FOREGROUND).toSolidColor().color
val colorPrimary = theme.getAttr(Theme.Attr.WINDOW_COLOR_PRIMARY).toSolidColor().color
tabLayout?.setTabTextColors(fgColor, fgColor)
tabLayout?.tabIconTint = ColorStateList.valueOf(fgColor)
tabLayout?.setSelectedTabIndicatorColor(colorPrimary)
switchToTextInputButton?.setTextColor(fgColor)
backspaceButton?.imageTintList = ColorStateList.valueOf(fgColor)
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val height = florisboard?.inputView?.desiredMediaKeyboardViewHeight ?: 0
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY))
val height = florisboard?.inputView?.desiredMediaKeyboardViewHeight ?: 0.0f
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(height.roundToInt(), MeasureSpec.EXACTLY))
}
}

View File

@@ -30,6 +30,9 @@ import androidx.core.graphics.BlendModeCompat
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.core.FlorisBoard
import dev.patrickgold.florisboard.ime.core.PrefHelper
import dev.patrickgold.florisboard.ime.text.key.KeyHintMode
import dev.patrickgold.florisboard.ime.theme.Theme
import dev.patrickgold.florisboard.ime.theme.ThemeManager
/**
* View class for managing the rendering and the events of a single emoji keyboard key.
@@ -44,7 +47,7 @@ class EmojiKeyView(
private val emojiKeyboardView: EmojiKeyboardView,
val data: EmojiKeyData
) : androidx.appcompat.widget.AppCompatTextView(emojiKeyboardView.context),
FlorisBoard.EventListener {
FlorisBoard.EventListener, ThemeManager.OnThemeUpdatedListener {
private val florisboard: FlorisBoard? = FlorisBoard.getInstanceOrNull()
private val prefs: PrefHelper = PrefHelper.getDefaultInstance(context)
@@ -92,8 +95,8 @@ class EmojiKeyView(
(parent.parent as ScrollView)
.requestDisallowInterceptTouchEvent(true)
emojiKeyboardView.isScrollBlocked = true
emojiKeyboardView.popupManager.show(this)
emojiKeyboardView.popupManager.extend(this)
emojiKeyboardView.popupManager.show(this, KeyHintMode.DISABLED)
emojiKeyboardView.popupManager.extend(this, KeyHintMode.DISABLED)
florisboard?.keyPressVibrate()
florisboard?.keyPressSound()
}, delayMillis.toLong())
@@ -146,10 +149,10 @@ class EmojiKeyView(
)
}
override fun onApplyThemeAttributes() {
override fun onThemeUpdated(theme: Theme) {
triangleDrawable?.colorFilter =
BlendModeColorFilterCompat.createBlendModeColorFilterCompat(
prefs.theme.mediaFgColorAlt, BlendModeCompat.SRC_ATOP
theme.getAttr(Theme.Attr.MEDIA_FOREGROUND_ALT).toSolidColor().color, BlendModeCompat.SRC_ATOP
)
}

View File

@@ -31,8 +31,9 @@ import com.google.android.flexbox.JustifyContent
import com.google.android.material.tabs.TabLayout
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.core.FlorisBoard
import dev.patrickgold.florisboard.ime.core.PrefHelper
import dev.patrickgold.florisboard.ime.popup.KeyPopupManager
import dev.patrickgold.florisboard.ime.popup.PopupManager
import dev.patrickgold.florisboard.ime.theme.Theme
import dev.patrickgold.florisboard.ime.theme.ThemeManager
import kotlinx.coroutines.*
import java.util.*
@@ -43,9 +44,10 @@ import java.util.*
*
* @property florisboard Reference to instance of core class [FlorisBoard].
*/
class EmojiKeyboardView : LinearLayout, FlorisBoard.EventListener {
class EmojiKeyboardView : LinearLayout, FlorisBoard.EventListener,
ThemeManager.OnThemeUpdatedListener {
private val florisboard: FlorisBoard? = FlorisBoard.getInstanceOrNull()
private val prefs: PrefHelper = PrefHelper.getDefaultInstance(context)
private val themeManager: ThemeManager = ThemeManager.default()
private var activeCategory: EmojiCategory = EmojiCategory.SMILEYS_EMOTION
private var emojiViewFlipper: ViewFlipper
@@ -57,7 +59,7 @@ class EmojiKeyboardView : LinearLayout, FlorisBoard.EventListener {
private val uiLayouts = EnumMap<EmojiCategory, ScrollView>(EmojiCategory::class.java)
var isScrollBlocked: Boolean = false
var popupManager = KeyPopupManager<EmojiKeyboardView, EmojiKeyView>(this)
var popupManager = PopupManager<EmojiKeyboardView, EmojiKeyView>(this, florisboard?.popupLayerView)
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
@@ -104,12 +106,18 @@ class EmojiKeyboardView : LinearLayout, FlorisBoard.EventListener {
override fun onAttachedToWindow() {
super.onAttachedToWindow()
themeManager.registerOnThemeUpdatedListener(this)
mainScope.launch {
layouts.await()
buildLayout()
setActiveCategory(EmojiCategory.SMILEYS_EMOTION)
themeManager.requestThemeUpdate(this@EmojiKeyboardView)
}
onApplyThemeAttributes()
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
themeManager.unregisterOnThemeUpdatedListener(this)
}
/**
@@ -216,8 +224,10 @@ class EmojiKeyboardView : LinearLayout, FlorisBoard.EventListener {
isScrollBlocked = true
}
override fun onApplyThemeAttributes() {
tabLayout.tabIconTint = ColorStateList.valueOf(prefs.theme.mediaFgColor)
tabLayout.setSelectedTabIndicatorColor(prefs.theme.colorAccent)
override fun onThemeUpdated(theme: Theme) {
val fgColor = theme.getAttr(Theme.Attr.MEDIA_FOREGROUND).toSolidColor().color
val colorAccent = theme.getAttr(Theme.Attr.WINDOW_COLOR_ACCENT).toSolidColor().color
tabLayout.tabIconTint = ColorStateList.valueOf(fgColor)
tabLayout.setSelectedTabIndicatorColor(colorAccent)
}
}

View File

@@ -21,6 +21,7 @@ import android.graphics.Paint
import android.graphics.Typeface
import android.util.Log
import androidx.core.graphics.PaintCompat
import timber.log.Timber
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStreamReader
@@ -171,13 +172,13 @@ fun parseRawEmojiSpecsFile(
}
}
} catch (e: IOException) {
Log.e("EmojiLayoutDataMap", "parseRawEmojiSpecsFile(): $e")
Timber.e("parseRawEmojiSpecsFile(): $e")
} finally {
if (reader != null) {
try {
reader.close()
} catch (e: IOException) {
Log.e("EmojiLayoutDataMap", "parseRawEmojiSpecsFile(): $e")
Timber.e("parseRawEmojiSpecsFile(): $e")
}
}
}

View File

@@ -68,7 +68,7 @@ class EmoticonKeyView : androidx.appcompat.widget.AppCompatTextView {
MotionEvent.ACTION_DOWN -> {
setBackgroundColor(getColorFromAttr(context, R.attr.semiTransparentColor))
florisboard.keyPressVibrate()
florisboard.keyPressSound(KeyData(0))
florisboard.keyPressSound(KeyData())
}
MotionEvent.ACTION_UP -> {
setBackgroundColor(Color.TRANSPARENT)

View File

@@ -1,77 +0,0 @@
/*
* Copyright (C) 2020 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.ime.popup
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.drawable.Drawable
import androidx.core.content.ContextCompat.getDrawable
import androidx.core.graphics.BlendModeColorFilterCompat
import androidx.core.graphics.BlendModeCompat
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.core.PrefHelper
import dev.patrickgold.florisboard.util.*
@SuppressLint("ViewConstructor")
class KeyPopupExtendedSingleView(
context: Context, val adjustedIndex: Int, var isActive: Boolean = false
) : androidx.appcompat.widget.AppCompatTextView(
context, null, 0
) {
private val prefs: PrefHelper = PrefHelper.getDefaultInstance(context)
var iconDrawable: Drawable? = null
init {
background = getDrawable(context, R.drawable.shape_rect_rounded)
}
override fun onDraw(canvas: Canvas?) {
setBackgroundTintColor2(this, when {
isActive -> prefs.theme.keyPopupBgColorActive
else -> Color.TRANSPARENT
})
setTextColor(prefs.theme.keyPopupFgColor)
super.onDraw(canvas)
canvas ?: return
val drawable = iconDrawable
val drawablePadding = (0.2f * measuredHeight).toInt()
if (drawable != null) {
var marginV = 0
var marginH = 0
if (measuredWidth > measuredHeight) {
marginH = (measuredWidth - measuredHeight) / 2
} else {
marginV = (measuredHeight - measuredWidth) / 2
}
drawable.setBounds(
marginH + drawablePadding,
marginV + drawablePadding,
measuredWidth - marginH - drawablePadding,
measuredHeight - marginV - drawablePadding)
drawable.colorFilter = BlendModeColorFilterCompat.createBlendModeColorFilterCompat(
prefs.theme.keyPopupFgColor,
BlendModeCompat.SRC_ATOP
)
drawable.draw(canvas)
}
}
}

View File

@@ -1,50 +0,0 @@
/*
* Copyright (C) 2020 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.ime.popup
import android.content.Context
import android.graphics.Canvas
import android.util.AttributeSet
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.core.PrefHelper
import dev.patrickgold.florisboard.util.*
class KeyPopupView : LinearLayout {
private val prefs: PrefHelper = PrefHelper.getDefaultInstance(context)
private lateinit var text: TextView
private lateinit var threedots: ImageView
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
override fun onAttachedToWindow() {
super.onAttachedToWindow()
text = findViewById(R.id.key_popup_text)
threedots = findViewById(R.id.key_popup_threedots)
}
override fun onDraw(canvas: Canvas?) {
setBackgroundTintColor2(this, prefs.theme.keyPopupBgColor)
text.setTextColor(prefs.theme.keyPopupFgColor)
setImageTintColor2(threedots, prefs.theme.keyPopupFgColor)
super.onDraw(canvas)
}
}

View File

@@ -0,0 +1,256 @@
/*
* Copyright (C) 2020 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.ime.popup
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Typeface
import android.graphics.drawable.Drawable
import android.graphics.drawable.PaintDrawable
import android.util.AttributeSet
import android.view.Gravity
import android.view.View
import android.widget.FrameLayout
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.theme.Theme
import dev.patrickgold.florisboard.ime.theme.ThemeManager
import dev.patrickgold.florisboard.util.ViewLayoutUtils
import kotlin.math.min
class PopupExtendedView : View, ThemeManager.OnThemeUpdatedListener {
private val themeManager: ThemeManager = ThemeManager.default()
private val activeBackgroundDrawable: PaintDrawable = PaintDrawable()
private var backgroundDrawable: PaintDrawable = PaintDrawable()
private val labelPaint: Paint = Paint().apply {
alpha = 255
color = 0
isAntiAlias = true
isFakeBoldText = false
textAlign = Paint.Align.CENTER
textSize = resources.getDimension(R.dimen.key_textSize)
typeface = Typeface.DEFAULT
}
private val tldPaint: Paint = Paint().apply {
alpha = 255
color = 0
isAntiAlias = true
isFakeBoldText = false
textAlign = Paint.Align.CENTER
textSize = resources.getDimension(R.dimen.key_textSize)
typeface = Typeface.DEFAULT
}
val properties: Properties = Properties(
width = resources.getDimension(R.dimen.key_width).toInt(),
height = resources.getDimension(R.dimen.key_height).toInt(),
xOffset = 0,
yOffset = 0,
gravity = Gravity.START,
elements = mutableListOf(),
activeElementIndex = -1,
labelTextSize = resources.getDimension(R.dimen.key_popup_textSize),
)
val isShowing: Boolean
get() = visibility == VISIBLE
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
)
init {
visibility = GONE
background = backgroundDrawable
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
themeManager.registerOnThemeUpdatedListener(this)
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
themeManager.unregisterOnThemeUpdatedListener(this)
}
override fun onThemeUpdated(theme: Theme) {
activeBackgroundDrawable.apply {
setTint(theme.getAttr(Theme.Attr.POPUP_BACKGROUND_ACTIVE).toSolidColor().color)
setCornerRadius(ViewLayoutUtils.convertDpToPixel(6.0f, context))
}
backgroundDrawable.apply {
setTint(theme.getAttr(Theme.Attr.POPUP_BACKGROUND).toSolidColor().color)
setCornerRadius(ViewLayoutUtils.convertDpToPixel(6.0f, context))
}
elevation = ViewLayoutUtils.convertDpToPixel(4.0f, context)
labelPaint.color = theme.getAttr(Theme.Attr.POPUP_FOREGROUND).toSolidColor().color
tldPaint.color = theme.getAttr(Theme.Attr.POPUP_FOREGROUND).toSolidColor().color
if (isShowing) {
invalidate()
}
}
private fun applyProperties(anchor: View) {
val anchorCoords = IntArray(2)
anchor.getLocationInWindow(anchorCoords)
val anchorX = anchorCoords[0]
val anchorY = anchorCoords[1] + anchor.measuredHeight
when (val lp = layoutParams) {
is FrameLayout.LayoutParams -> lp.apply {
width = properties.width
height = properties.height
setMargins(
anchorX + properties.xOffset,
anchorY + properties.yOffset,
0,
0
)
}
else -> {
layoutParams = FrameLayout.LayoutParams(properties.width, properties.height).apply {
setMargins(
anchorX + properties.xOffset,
anchorY + properties.yOffset,
0,
0
)
}
}
}
labelPaint.textSize = properties.labelTextSize
tldPaint.textSize = properties.labelTextSize * 0.6f
if (isShowing) {
requestLayout()
invalidate()
}
}
fun show(anchor: View) {
applyProperties(anchor)
visibility = VISIBLE
requestLayout()
invalidate()
}
fun hide() {
visibility = GONE
requestLayout()
invalidate()
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas ?: return
if (properties.elements.isEmpty() || properties.elements.first().isEmpty()) {
return
}
val baseSize = properties.elements.first().size
val elementWidth = measuredWidth / baseSize
val elementHeight = measuredHeight / properties.elements.size
var currentElementIndex = 0
for ((r, row) in properties.elements.reversed().withIndex()) {
val leftOffset = when (properties.gravity) {
Gravity.END -> baseSize - row.size
else -> 0
}
for ((e, element) in row.withIndex()) {
val left = (e + leftOffset) * elementWidth
val top = r * elementHeight
if (properties.activeElementIndex == currentElementIndex) {
activeBackgroundDrawable.setBounds(
left, top, left + elementWidth, top + elementHeight
)
activeBackgroundDrawable.draw(canvas)
}
when (element) {
is Element.Label -> {
val label = element.label
if (label.isNotEmpty()) {
val centerX = left + elementWidth / 2.0f
val centerY = top + elementHeight / 2.0f + (labelPaint.textSize - labelPaint.descent()) / 2
canvas.drawText(label, centerX, centerY, labelPaint)
}
}
is Element.Tld -> {
val tld = element.tld
if (tld.isNotEmpty()) {
val centerX = left + elementWidth / 2.0f
val centerY = top + elementHeight / 2.0f + (tldPaint.textSize - tldPaint.descent()) / 2
canvas.drawText(tld, centerX, centerY, tldPaint)
}
}
is Element.Icon -> {
val drawable = element.icon
drawable.setTint(labelPaint.color)
val drawableSize = (min(elementWidth, elementHeight) * 0.6f).toInt()
val drawablePaddingLeft = ((elementWidth - drawableSize) / 2.0f).toInt()
val drawablePaddingTop = ((elementHeight - drawableSize) / 2.0f).toInt()
drawable.setBounds(
left + drawablePaddingLeft,
top + drawablePaddingTop,
left + drawablePaddingLeft + drawableSize,
top + drawablePaddingTop + drawableSize
)
drawable.draw(canvas)
}
else -> {}
}
currentElementIndex++
}
}
}
data class Properties(
var width: Int,
var height: Int,
var xOffset: Int,
var yOffset: Int,
var gravity: Int,
var elements: MutableList<MutableList<Element>>,
var activeElementIndex: Int,
var labelTextSize: Float
) {
fun getElementOrNull(index: Int = activeElementIndex): Element? {
if (index < 0) {
return null
}
var cachedIndex = index
elements.reversed().forEach { row ->
if (cachedIndex >= row.size) {
cachedIndex -= row.size
} else {
return row[cachedIndex]
}
}
return null
}
}
sealed class Element(val adjustedIndex: Int) {
class Label(val label: String, adjustedIndex: Int) : Element(adjustedIndex)
class Tld(val tld: String, adjustedIndex: Int) : Element(adjustedIndex)
class Icon(val icon: Drawable, adjustedIndex: Int) : Element(adjustedIndex)
object Undefined : Element(-1)
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright (C) 2020 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.ime.popup
import dev.patrickgold.florisboard.ime.extension.Asset
import dev.patrickgold.florisboard.ime.text.key.KeyData
import dev.patrickgold.florisboard.ime.text.key.KeyVariation
/**
* An object which maps each base key to its extended popups. This can be done for each
* key variation. [KeyVariation.ALL] is always the fallback for each key.
*/
typealias PopupMapping = Map<KeyVariation, Map<String, PopupSet<KeyData>>>
/**
* Class which contains an extended popup mapping to use for adding popups subtype based on the
* keyboard layout.
*
* @property mapping The mapping of the base keys to their popups. See [PopupMapping] for more info.
*/
class PopupExtension(
override val name: String,
override val label: String = name,
override val authors: List<String>,
val mapping: PopupMapping
) : Asset {
companion object : Asset.Companion<PopupExtension> {
override fun empty() = PopupExtension("", "", listOf(), mapOf())
}
}

View File

@@ -16,22 +16,32 @@
package dev.patrickgold.florisboard.ime.popup
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Canvas
import android.util.AttributeSet
import com.google.android.flexbox.FlexboxLayout
import android.view.MotionEvent
import android.widget.FrameLayout
import dev.patrickgold.florisboard.ime.core.PrefHelper
import dev.patrickgold.florisboard.util.*
class KeyPopupExtendedView : FlexboxLayout {
class PopupLayerView : FrameLayout {
private val prefs: PrefHelper = PrefHelper.getDefaultInstance(context)
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
override fun onDraw(canvas: Canvas?) {
setBackgroundTintColor2(this, prefs.theme.keyPopupBgColor)
super.onDraw(canvas)
init {
background = null
isClickable = false
isFocusable = false
}
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
return true
}
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent?): Boolean {
return false
}
}

View File

@@ -17,22 +17,15 @@
package dev.patrickgold.florisboard.ime.popup
import android.content.res.Configuration
import android.util.TypedValue
import android.view.Gravity
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.widget.*
import android.view.*
import androidx.core.content.ContextCompat.getDrawable
import androidx.core.view.get
import com.google.android.flexbox.FlexboxLayout
import com.google.android.flexbox.JustifyContent
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.media.emoji.EmojiKeyData
import dev.patrickgold.florisboard.ime.media.emoji.EmojiKeyView
import dev.patrickgold.florisboard.ime.media.emoji.EmojiKeyboardView
import dev.patrickgold.florisboard.ime.text.key.KeyCode
import dev.patrickgold.florisboard.ime.text.key.KeyData
import dev.patrickgold.florisboard.ime.text.key.KeyHintMode
import dev.patrickgold.florisboard.ime.text.key.KeyView
import dev.patrickgold.florisboard.ime.text.keyboard.KeyboardView
@@ -42,11 +35,13 @@ import dev.patrickgold.florisboard.ime.text.keyboard.KeyboardView
*
* @property keyboardView Reference to the keyboard view to which this manager class belongs to.
*/
class KeyPopupManager<T_KBD: View, T_KV: View>(private val keyboardView: T_KBD) {
class PopupManager<T_KBD: View, T_KV: View>(
private val keyboardView: T_KBD,
private val popupLayerView: PopupLayerView?
) {
private var anchorLeft: Boolean = false
private var anchorRight: Boolean = false
private var anchorOffset: Int = 0
private var activeExtIndex: Int? = null
private val exceptionsForKeyCodes = listOf(
KeyCode.ENTER,
KeyCode.LANGUAGE_SWITCH,
@@ -55,111 +50,87 @@ class KeyPopupManager<T_KBD: View, T_KV: View>(private val keyboardView: T_KBD)
)
private var keyPopupWidth: Int
private var keyPopupHeight: Int
var keyPopupTextSize: Float = keyboardView.resources.getDimension(R.dimen.key_popup_textSize)
private var keyPopupDiffX: Int = 0
private val popupView: LinearLayout
private val popupViewExt: FlexboxLayout
private val popupView: PopupView
private val popupViewExt: PopupExtendedView
private var row0count: Int = 0
private var row1count: Int = 0
private var window: PopupWindow
private var windowExt: PopupWindow
/** Is true if the preview popup is visible to the user, else false */
val isShowingPopup: Boolean
get() = popupView.visibility == View.VISIBLE
get() = popupView.isShowing
/** Is true if the extended popup is visible to the user, else false */
val isShowingExtendedPopup: Boolean
get() = windowExt.isShowing
get() = popupViewExt.isShowing
companion object {
const val POPUP_EXTENSION_PATH_REL: String = "ime/text/characters/extended_popups"
}
init {
keyPopupWidth = keyboardView.resources.getDimension(R.dimen.key_width).toInt()
keyPopupHeight = keyboardView.resources.getDimension(R.dimen.key_height).toInt()
popupView = View.inflate(
keyboardView.context,
R.layout.key_popup, null
) as LinearLayout
popupView.visibility = View.INVISIBLE
popupViewExt = View.inflate(
keyboardView.context,
R.layout.key_popup_extended, null
) as FlexboxLayout
window = createPopupWindow(popupView)
windowExt = createPopupWindow(popupViewExt)
popupView = PopupView(keyboardView.context)
popupViewExt = PopupExtendedView(keyboardView.context)
popupLayerView?.addView(popupView)
popupLayerView?.addView(popupViewExt)
}
/**
* Helper function to create a [KeyPopupExtendedSingleView] and preconfigure it.
* Helper function to create a element for the extended popup and preconfigure it.
*
* @param keyView Reference to the keyView currently controlling the popup.
* @param k The index of the key in the key data popup array.
* @param isInitActive If it should initially be marked as active.
* @param isWrapBefore If the [FlexboxLayout] should wrap before this view.
* @return A preconfigured [KeyPopupExtendedSingleView].
* @param adjustedIndex The index of the key in the key data popup array.
* @return A preconfigured extended popup element.
*/
private fun createTextView(
private fun createElement(
keyView: T_KV,
k: Int,
isInitActive: Boolean = false,
isWrapBefore: Boolean = false
): KeyPopupExtendedSingleView? {
val textView = KeyPopupExtendedSingleView(keyView.context, k, isInitActive)
val lp = FlexboxLayout.LayoutParams(keyPopupWidth, keyView.measuredHeight)
lp.isWrapBefore = isWrapBefore
textView.layoutParams = lp
textView.gravity = Gravity.CENTER
val textSize = keyboardView.resources.getDimension(R.dimen.key_popup_textSize)
if (keyView is KeyView) {
when (keyView.dataPopupWithHint[k].code) {
KeyCode.SETTINGS -> {
textView.iconDrawable = getDrawable(
keyView.context, R.drawable.ic_settings
)
}
KeyCode.SWITCH_TO_TEXT_CONTEXT -> {
textView.text = keyView.resources.getString(R.string.key__view_characters)
}
KeyCode.SWITCH_TO_MEDIA_CONTEXT -> {
textView.iconDrawable = getDrawable(
keyView.context, R.drawable.ic_sentiment_satisfied
)
}
KeyCode.TOGGLE_ONE_HANDED_MODE -> {
textView.iconDrawable = getDrawable(
keyView.context, R.drawable.ic_keyboard_arrow_right
)
}
else -> {
textView.setTextSize(
TypedValue.COMPLEX_UNIT_PX, when (keyView.dataPopupWithHint[k].code) {
KeyCode.URI_COMPONENT_TLD,
KeyCode.SWITCH_TO_TEXT_CONTEXT -> textSize * 0.6f
else -> textSize
}
)
textView.text = keyView.getComputedLetter(keyView.dataPopupWithHint[k])
adjustedIndex: Int
): PopupExtendedView.Element {
return when (keyView) {
is KeyView -> {
when (keyView.data.popup[adjustedIndex].code) {
KeyCode.SETTINGS -> {
getDrawable(keyView.context, R.drawable.ic_settings)?.let {
PopupExtendedView.Element.Icon(it, adjustedIndex)
} ?: PopupExtendedView.Element.Undefined
}
KeyCode.SWITCH_TO_TEXT_CONTEXT -> {
PopupExtendedView.Element.Label(
keyView.resources.getString(R.string.key__view_characters), adjustedIndex
)
}
KeyCode.SWITCH_TO_MEDIA_CONTEXT -> {
getDrawable(keyView.context, R.drawable.ic_sentiment_satisfied)?.let {
PopupExtendedView.Element.Icon(it, adjustedIndex)
} ?: PopupExtendedView.Element.Undefined
}
KeyCode.URI_COMPONENT_TLD -> {
PopupExtendedView.Element.Tld(
keyView.data.popup[adjustedIndex].label, adjustedIndex
)
}
KeyCode.TOGGLE_ONE_HANDED_MODE -> {
getDrawable(keyView.context, R.drawable.ic_smartphone)?.let {
PopupExtendedView.Element.Icon(it, adjustedIndex)
} ?: PopupExtendedView.Element.Undefined
}
else -> {
PopupExtendedView.Element.Label(
keyView.getComputedLetter(keyView.data.popup[adjustedIndex]), adjustedIndex
)
}
}
}
} else if (keyView is EmojiKeyView) {
textView.text = keyView.data.popup[k].getCodePointsAsString()
}
return textView
}
/**
* Helper function for a convenient way of creating a [PopupWindow].
*
* @param view The view to set as content view of the [PopupWindow].
* @return A new [PopupWindow] already preconfigured and ready-to-go.
*/
private fun createPopupWindow(view: View): PopupWindow {
return PopupWindow(keyboardView.context).apply {
animationStyle = 0
contentView = view
enterTransition = null
exitTransition = null
isClippingEnabled = false
isFocusable = false
isTouchable = false
setBackgroundDrawable(null)
is EmojiKeyView -> {
PopupExtendedView.Element.Label(
keyView.data.popup[adjustedIndex].getCodePointsAsString(), adjustedIndex
)
}
else -> {
PopupExtendedView.Element.Undefined
}
}
}
@@ -171,12 +142,22 @@ class KeyPopupManager<T_KBD: View, T_KV: View>(private val keyboardView: T_KBD)
if (keyboardView is KeyboardView) {
when (keyboardView.resources.configuration.orientation) {
Configuration.ORIENTATION_LANDSCAPE -> {
keyPopupWidth = (keyboardView.desiredKeyWidth * 0.6f).toInt()
keyPopupHeight = (keyboardView.desiredKeyHeight * 3.0f).toInt()
if (keyboardView.isSmartbarKeyboardView) {
keyPopupWidth = (keyView.measuredWidth * 0.6f).toInt()
keyPopupHeight = (keyboardView.desiredKeyHeight * 3.0f * 1.2f).toInt()
} else {
keyPopupWidth = (keyboardView.desiredKeyWidth * 0.6f).toInt()
keyPopupHeight = (keyboardView.desiredKeyHeight * 3.0f).toInt()
}
}
else -> {
keyPopupWidth = (keyboardView.desiredKeyWidth * 1.1f).toInt()
keyPopupHeight = (keyboardView.desiredKeyHeight * 2.5f).toInt()
if (keyboardView.isSmartbarKeyboardView) {
keyPopupWidth = (keyView.measuredWidth * 1.1f).toInt()
keyPopupHeight = (keyboardView.desiredKeyHeight * 2.5f * 1.2f).toInt()
} else {
keyPopupWidth = (keyboardView.desiredKeyWidth * 1.1f).toInt()
keyPopupHeight = (keyboardView.desiredKeyHeight * 2.5f).toInt()
}
}
}
} else if (keyboardView is EmojiKeyboardView) {
@@ -192,36 +173,32 @@ class KeyPopupManager<T_KBD: View, T_KV: View>(private val keyboardView: T_KBD)
*
* @param keyView Reference to the keyView currently controlling the popup.
*/
fun show(keyView: T_KV) {
fun show(keyView: T_KV, keyHintMode: KeyHintMode) {
if (keyView is KeyView && keyView.data.code <= KeyCode.SPACE) {
return
}
calc(keyView)
val keyPopupX = keyPopupDiffX
val keyPopupY = -keyPopupHeight
if (window.isShowing) {
window.update(keyView, keyPopupX, keyPopupY, keyPopupWidth, keyPopupHeight)
} else {
window.width = keyPopupWidth
window.height = keyPopupHeight
window.showAsDropDown(keyView, keyPopupX, keyPopupY, Gravity.NO_GRAVITY)
}
if (keyView is KeyView) {
popupView.findViewById<TextView>(R.id.key_popup_text)?.text = keyView.getComputedLetter()
popupView.findViewById<ImageView>(R.id.key_popup_threedots)?.visibility = when {
keyView.dataPopupWithHint.isEmpty() -> View.INVISIBLE
else -> View.VISIBLE
popupView.properties.apply {
width = keyPopupWidth
height = keyPopupHeight
xOffset = keyPopupDiffX
yOffset = -keyPopupHeight
innerLabelFactor = 0.4f
label = when (keyView) {
is KeyView -> keyView.getComputedLetter()
is EmojiKeyView -> keyView.data.getCodePointsAsString()
else -> ""
}
} else if (keyView is EmojiKeyView) {
popupView.findViewById<TextView>(R.id.key_popup_text)?.text = keyView.data.getCodePointsAsString()
popupView.findViewById<ImageView>(R.id.key_popup_threedots)?.visibility = when {
keyView.data.popup.isEmpty() -> View.INVISIBLE
else -> View.VISIBLE
labelTextSize = keyPopupTextSize
shouldIndicateExtendedPopups = when (keyView) {
is KeyView -> keyView.data.popup.size(keyHintMode) > 0
is EmojiKeyView -> keyView.data.popup.isNotEmpty()
else -> false
}
}
popupView.visibility = View.VISIBLE
popupView.show(keyView)
}
/**
@@ -245,7 +222,7 @@ class KeyPopupManager<T_KBD: View, T_KV: View>(private val keyboardView: T_KBD)
*
* @param keyView Reference to the keyView currently controlling the popup.
*/
fun extend(keyView: T_KV) {
fun extend(keyView: T_KV, keyHintMode: KeyHintMode) {
if (keyView is KeyView && keyView.data.code <= KeyCode.SPACE
&& !exceptionsForKeyCodes.contains(keyView.data.code)) {
return
@@ -261,11 +238,12 @@ class KeyPopupManager<T_KBD: View, T_KV: View>(private val keyboardView: T_KBD)
// Determine key counts for each row
val n = when (keyView) {
is KeyView -> keyView.dataPopupWithHint.size
is KeyView -> keyView.data.popup.size(keyHintMode)
is EmojiKeyView -> keyView.data.popup.size
else -> 0
}
when {
n <= 0 -> return
n <= 5 -> {
row1count = 0
row0count = n
@@ -308,76 +286,106 @@ class KeyPopupManager<T_KBD: View, T_KV: View>(private val keyboardView: T_KBD)
}
// Build UI
popupViewExt.removeAllViews()
val indices = when (keyView) {
is KeyView -> keyView.dataPopupWithHint.indices
is EmojiKeyView -> keyView.data.popup.indices
else -> IntRange(0, 0)
popupViewExt.properties.elements.clear()
val initUiIndex = when {
anchorLeft -> anchorOffset + row1count
anchorRight -> row0count - 1 - anchorOffset + row1count
else -> 0
}
var hasShownFirst = false
for (k in indices) {
val isInitActive =
anchorLeft && (k - row1count == anchorOffset) ||
anchorRight && (k - row1count == row0count - 1 - anchorOffset)
val kk = when (keyView) {
is KeyView -> when {
isInitActive -> {
hasShownFirst = true
0
val popupIndices: IntArray
val uiIndices = IntRange(0, (n - 1).coerceAtLeast(0))
if (keyView is KeyView) {
popupIndices = IntArray(n) { 0 }
when (keyHintMode) {
KeyHintMode.ENABLED_ACCENT_PRIORITY -> when {
keyView.data.popup.main != null -> {
popupIndices[initUiIndex] = PopupSet.MAIN_INDEX
if (keyView.data.popup.hint != null) when {
initUiIndex + 1 < n -> popupIndices[initUiIndex + 1] = PopupSet.HINT_INDEX
initUiIndex - 1 >= 0 -> popupIndices[initUiIndex - 1] = PopupSet.HINT_INDEX
}
}
keyView.data.popup.hint != null -> when {
initUiIndex + 1 < n -> popupIndices[initUiIndex + 1] = PopupSet.HINT_INDEX
initUiIndex - 1 >= 0 -> popupIndices[initUiIndex - 1] = PopupSet.HINT_INDEX
else -> popupIndices[initUiIndex] = PopupSet.HINT_INDEX
}
hasShownFirst -> k
else -> k + 1
}
else -> k
KeyHintMode.ENABLED_HINT_PRIORITY -> when {
keyView.data.popup.hint != null -> {
popupIndices[initUiIndex] = PopupSet.HINT_INDEX
if (keyView.data.popup.main != null) when {
initUiIndex + 1 < n -> popupIndices[initUiIndex + 1] = PopupSet.MAIN_INDEX
initUiIndex - 1 >= 0 -> popupIndices[initUiIndex - 1] = PopupSet.MAIN_INDEX
}
}
keyView.data.popup.main != null -> popupIndices[initUiIndex] = PopupSet.MAIN_INDEX
}
KeyHintMode.ENABLED_SMART_PRIORITY -> when {
keyView.data.popup.main != null -> {
popupIndices[initUiIndex] = PopupSet.MAIN_INDEX
if (keyView.data.popup.hint != null) when {
initUiIndex + 1 < n -> popupIndices[initUiIndex + 1] = PopupSet.HINT_INDEX
initUiIndex - 1 >= 0 -> popupIndices[initUiIndex - 1] = PopupSet.HINT_INDEX
}
}
keyView.data.popup.hint != null -> popupIndices[initUiIndex] = PopupSet.HINT_INDEX
}
KeyHintMode.DISABLED -> when {
keyView.data.popup.main != null -> popupIndices[initUiIndex] = PopupSet.MAIN_INDEX
}
}
popupViewExt.addView(
createTextView(
keyView, kk, isInitActive, (row1count > 0) && (k - row1count == 0)
)
)
if (isInitActive) {
activeExtIndex = k
var offset = 0
for (uiIndex in uiIndices) {
if (popupIndices[uiIndex] < 0) {
offset++
} else {
popupIndices[uiIndex] = uiIndex - offset
}
}
} else {
popupIndices = IntArray(n) { it }
}
if (row1count > 0) {
popupViewExt.properties.elements.add(mutableListOf())
}
popupViewExt.properties.elements.add(mutableListOf())
for (uiIndex in uiIndices) {
val rowIndex = if (uiIndex < row1count && row1count > 0) { 1 } else { 0 }
popupViewExt.properties.elements[rowIndex].add(
createElement(keyView, popupIndices[uiIndex])
)
}
popupView.findViewById<ImageView>(R.id.key_popup_threedots)?.visibility = View.INVISIBLE
// Calculate layout params
val extWidth = row0count * keyPopupWidth
val extHeight = when {
row1count > 0 -> keyView.measuredHeight * 2
else -> keyView.measuredHeight
}
popupViewExt.justifyContent = if (anchorLeft) {
JustifyContent.FLEX_START
} else {
JustifyContent.FLEX_END
}
if (popupViewExt.layoutParams == null) {
popupViewExt.layoutParams = ViewGroup.LayoutParams(extWidth, extHeight)
} else {
popupViewExt.layoutParams.apply {
width = extWidth
height = extHeight
}
}
row1count > 0 -> keyPopupHeight * 0.4f * 2.0f
else -> keyPopupHeight * 0.4f
}.toInt()
val x = ((keyView.measuredWidth - keyPopupWidth) / 2) + when {
anchorLeft -> -anchorOffset * keyPopupWidth
anchorRight -> -extWidth + keyPopupWidth + anchorOffset * keyPopupWidth
else -> 0
}
val y = -keyPopupHeight - when {
row1count > 0 -> keyView.measuredHeight
row1count > 0 -> (keyPopupHeight * 0.4f).toInt()
else -> 0
}
// Position and show popup window
if (windowExt.isShowing) {
windowExt.update(keyView, x, y, extWidth, extHeight)
} else {
windowExt.width = extWidth
windowExt.height = extHeight
windowExt.showAsDropDown(keyView, x, y, Gravity.NO_GRAVITY)
popupViewExt.properties.apply {
width = extWidth
height = extHeight
xOffset = x
yOffset = y
gravity = if (anchorLeft) { Gravity.START } else { Gravity.END }
labelTextSize = keyPopupTextSize
activeElementIndex = initUiIndex
}
popupViewExt.show(keyView)
popupView.properties.shouldIndicateExtendedPopups = false
popupView.invalidate()
}
/**
@@ -400,7 +408,7 @@ class KeyPopupManager<T_KBD: View, T_KV: View>(private val keyboardView: T_KBD)
return false
}
activeExtIndex = when {
popupViewExt.properties.activeElementIndex = when {
anchorLeft -> when {
// check if out of boundary on x-axis
event.x < keyPopupDiffX - (anchorOffset + 1) * keyPopupWidth ||
@@ -446,24 +454,7 @@ class KeyPopupManager<T_KBD: View, T_KV: View>(private val keyboardView: T_KBD)
}
else -> -1
}
if (keyView is KeyView) {
for (k in keyView.dataPopupWithHint.indices) {
val view = popupViewExt.getChildAt(k)
if (view != null) {
val textView = view as KeyPopupExtendedSingleView
textView.isActive = k == activeExtIndex
}
}
} else if (keyView is EmojiKeyView) {
for (k in keyView.data.popup.indices) {
val view = popupViewExt.getChildAt(k)
if (view != null) {
val textView = view as KeyPopupExtendedSingleView
textView.isActive = k == activeExtIndex
}
}
}
popupViewExt.invalidate()
return true
}
@@ -478,10 +469,9 @@ class KeyPopupManager<T_KBD: View, T_KV: View>(private val keyboardView: T_KBD)
*/
fun getActiveKeyData(keyView: T_KV): KeyData? {
return if (keyView is KeyView) {
val activeExtIndex = activeExtIndex
if (activeExtIndex != null) {
val singleView = popupViewExt[activeExtIndex] as KeyPopupExtendedSingleView
keyView.dataPopupWithHint.getOrNull(singleView.adjustedIndex) ?: keyView.data
val element = popupViewExt.properties.getElementOrNull()
if (element != null) {
keyView.data.popup.getOrNull(element.adjustedIndex) ?: keyView.data
} else {
keyView.data
}
@@ -500,7 +490,12 @@ class KeyPopupManager<T_KBD: View, T_KV: View>(private val keyboardView: T_KBD)
*/
fun getActiveEmojiKeyData(keyView: T_KV): EmojiKeyData? {
return if (keyView is EmojiKeyView) {
keyView.data.popup.getOrNull(activeExtIndex ?: -1) ?: keyView.data
val element = popupViewExt.properties.getElementOrNull()
if (element != null) {
keyView.data.popup.getOrNull(element.adjustedIndex) ?: keyView.data
} else {
keyView.data
}
} else {
null
}
@@ -510,12 +505,9 @@ class KeyPopupManager<T_KBD: View, T_KV: View>(private val keyboardView: T_KBD)
* Hides the key preview popup as well as the extended popup.
*/
fun hide() {
popupView.visibility = View.INVISIBLE
if (windowExt.isShowing) {
windowExt.dismiss()
}
activeExtIndex = null
popupView.hide()
popupViewExt.hide()
popupViewExt.properties.activeElementIndex = -1
}
/**
@@ -523,13 +515,10 @@ class KeyPopupManager<T_KBD: View, T_KV: View>(private val keyboardView: T_KBD)
* is closing.
*/
fun dismissAllPopups() {
if (window.isShowing) {
window.dismiss()
}
if (windowExt.isShowing) {
windowExt.dismiss()
}
activeExtIndex = null
popupView.hide()
popupLayerView?.removeView(popupView)
popupViewExt.hide()
popupViewExt.properties.activeElementIndex = -1
popupLayerView?.removeView(popupViewExt)
}
}

View File

@@ -0,0 +1,119 @@
/*
* Copyright (C) 2020 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.ime.popup
import dev.patrickgold.florisboard.ime.text.key.KeyHintMode
/**
* A popup set for a single key. This set describes, if the key has a [hint] character,
* a [main] character and other [relevant] popups.
*
* Note, that a hint character should **never** be set in a json extended popup file, rather it
* should only be dynamically set by the LayoutManager.
*
* The order in which these defined popups will be shown depends on the current [KeyHintMode],
* al well as the calculations made by the KeyPopupManager.
*
* The popup set can be accessed like an array with the addition that negative indexes defined
* within this companion object are allowed (as long as the corresponding [hint] or [main]
* character is *not* null).
*/
class PopupSet<T> (
var hint: T? = null,
var main: T? = null,
var relevant: List<T> = listOf()
) : Collection<T> {
companion object {
const val HINT_INDEX: Int = -2
const val MAIN_INDEX: Int = -1
}
override val size: Int
get() = if (hint != null) { 1 } else { 0 } + if (main != null) { 1 } else { 0 } +
relevant.size
fun size(keyHintMode: KeyHintMode): Int {
return if (keyHintMode == KeyHintMode.DISABLED && hint != null) {
size - 1
} else {
size
}
}
operator fun get(index: Int): T {
val item = getOrNull(index)
if (item == null) {
throw IndexOutOfBoundsException(
"Specified index $index is not an valid entry in this PopupSet!"
)
} else {
return item
}
}
fun getOrNull(index: Int): T? {
if (index >= relevant.size || index < HINT_INDEX) {
return null
}
return when (index) {
HINT_INDEX -> hint
MAIN_INDEX -> main
else -> relevant.getOrNull(index)
}
}
override fun contains(element: T): Boolean {
return hint == element || main == element || relevant.contains(element)
}
override fun containsAll(elements: Collection<T>): Boolean {
for (element in elements) {
if (!contains(element)) {
return false
}
}
return true
}
override fun iterator(): Iterator<T> {
TODO("Not yet implemented")
}
override fun isEmpty(): Boolean {
return size == 0
}
fun merge(other: PopupSet<T>) {
val tempRelevant = relevant.toMutableList()
tempRelevant.addAll(other.relevant)
other.hint?.let {
if (hint == null) {
hint = it
} else {
tempRelevant.add(it)
}
}
other.main?.let {
if (main == null) {
main = it
} else {
tempRelevant.add(it)
}
}
relevant = tempRelevant.toList()
}
}

View File

@@ -0,0 +1,186 @@
/*
* Copyright (C) 2020 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.ime.popup
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Typeface
import android.graphics.drawable.Drawable
import android.graphics.drawable.PaintDrawable
import android.util.AttributeSet
import android.view.View
import android.widget.FrameLayout
import androidx.core.content.ContextCompat
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.theme.Theme
import dev.patrickgold.florisboard.ime.theme.ThemeManager
import dev.patrickgold.florisboard.util.ViewLayoutUtils
class PopupView : View, ThemeManager.OnThemeUpdatedListener {
private val themeManager: ThemeManager = ThemeManager.default()
private var backgroundDrawable: PaintDrawable = PaintDrawable()
private val labelPaint: Paint = Paint().apply {
alpha = 255
color = 0
isAntiAlias = true
isFakeBoldText = false
textAlign = Paint.Align.CENTER
textSize = resources.getDimension(R.dimen.key_textSize)
typeface = Typeface.DEFAULT
}
private val threeDotsDrawable: Drawable? =
ContextCompat.getDrawable(context, R.drawable.ic_more_horiz)
val properties: Properties = Properties(
width = resources.getDimension(R.dimen.key_width).toInt(),
height = resources.getDimension(R.dimen.key_height).toInt(),
xOffset = 0,
yOffset = 0,
innerLabelFactor = 0.4f,
label = "",
labelTextSize = resources.getDimension(R.dimen.key_popup_textSize),
shouldIndicateExtendedPopups = false
)
val isShowing: Boolean
get() = visibility == VISIBLE
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
)
init {
visibility = GONE
background = backgroundDrawable
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
themeManager.registerOnThemeUpdatedListener(this)
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
themeManager.unregisterOnThemeUpdatedListener(this)
}
override fun onThemeUpdated(theme: Theme) {
backgroundDrawable.apply {
setTint(theme.getAttr(Theme.Attr.POPUP_BACKGROUND).toSolidColor().color)
setCornerRadius(ViewLayoutUtils.convertDpToPixel(6.0f, context))
}
elevation = ViewLayoutUtils.convertDpToPixel(4.0f, context)
threeDotsDrawable?.apply {
setTint(theme.getAttr(Theme.Attr.POPUP_FOREGROUND).toSolidColor().color)
}
labelPaint.color = theme.getAttr(Theme.Attr.POPUP_FOREGROUND).toSolidColor().color
if (isShowing) {
invalidate()
}
}
private fun applyProperties(anchor: View) {
val anchorCoords = IntArray(2)
anchor.getLocationInWindow(anchorCoords)
val anchorX = anchorCoords[0]
val anchorY = anchorCoords[1] + anchor.measuredHeight
when (val lp = layoutParams) {
is FrameLayout.LayoutParams -> lp.apply {
width = properties.width
height = properties.height
setMargins(
anchorX + properties.xOffset,
anchorY + properties.yOffset,
0,
0
)
}
else -> {
layoutParams = FrameLayout.LayoutParams(properties.width, properties.height).apply {
setMargins(
anchorX + properties.xOffset,
anchorY + properties.yOffset,
0,
0
)
}
}
}
labelPaint.textSize = properties.labelTextSize
if (isShowing) {
requestLayout()
invalidate()
}
}
fun show(anchor: View) {
applyProperties(anchor)
visibility = VISIBLE
requestLayout()
invalidate()
}
fun hide() {
visibility = GONE
requestLayout()
invalidate()
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas ?: return
// Draw label
val label = properties.label
if (label.isNotEmpty()) {
val centerX = measuredWidth / 2.0f
val centerY =
(measuredHeight * properties.innerLabelFactor) / 2.0f + (labelPaint.textSize - labelPaint.descent()) / 2
canvas.drawText(label, centerX, centerY, labelPaint)
}
// Draw drawable
val drawable = threeDotsDrawable
if (drawable != null && properties.shouldIndicateExtendedPopups) {
val marginTop = measuredHeight * properties.innerLabelFactor
val drawableSize = marginTop * 0.25f
drawable.setBounds(
(measuredWidth * 0.95f - drawableSize).toInt(),
marginTop.toInt(),
(measuredWidth * 0.95f).toInt(),
(marginTop + drawableSize).toInt()
)
drawable.draw(canvas)
}
}
data class Properties(
var width: Int,
var height: Int,
var xOffset: Int,
var yOffset: Int,
var innerLabelFactor: Float,
var label: String,
var labelTextSize: Float,
var shouldIndicateExtendedPopups: Boolean
)
}

View File

@@ -23,6 +23,7 @@ import android.content.res.Configuration
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Typeface
import android.os.Handler
import android.util.AttributeSet
import android.view.MotionEvent
import android.widget.Button
@@ -32,17 +33,22 @@ import dev.patrickgold.florisboard.ime.core.FlorisBoard
import dev.patrickgold.florisboard.ime.core.PrefHelper
import dev.patrickgold.florisboard.ime.text.key.KeyCode
import dev.patrickgold.florisboard.ime.text.key.KeyData
import java.util.*
import dev.patrickgold.florisboard.ime.theme.Theme
import dev.patrickgold.florisboard.ime.theme.ThemeManager
import dev.patrickgold.florisboard.ime.theme.ThemeValue
import dev.patrickgold.florisboard.util.cancelAll
import dev.patrickgold.florisboard.util.postAtScheduledRate
/**
* View class for managing and rendering an editing key.
*/
class EditingKeyView : AppCompatImageButton {
class EditingKeyView : AppCompatImageButton, ThemeManager.OnThemeUpdatedListener {
private val florisboard: FlorisBoard? = FlorisBoard.getInstanceOrNull()
private val prefs: PrefHelper = PrefHelper.getDefaultInstance(context)
private val themeManager: ThemeManager = ThemeManager.default()
private val data: KeyData
private var isKeyPressed: Boolean = false
private var osTimer: Timer? = null
private val repeatedKeyPressHandler: Handler = Handler(context.mainLooper)
private var label: String? = null
private var labelPaint: Paint = Paint().apply {
@@ -55,6 +61,10 @@ class EditingKeyView : AppCompatImageButton {
typeface = Typeface.DEFAULT
}
private var colorHighlightedEnabled: ThemeValue = ThemeValue.SolidColor(0)
private var colorEnabled: ThemeValue = ThemeValue.SolidColor(0)
private var colorDefault: ThemeValue = ThemeValue.SolidColor(0)
var isHighlighted: Boolean = false
set(value) { field = value; invalidate() }
@@ -76,13 +86,23 @@ class EditingKeyView : AppCompatImageButton {
R.id.select_all -> KeyCode.CLIPBOARD_SELECT_ALL
else -> 0
}
data = KeyData(code)
data = KeyData(code = code)
context.obtainStyledAttributes(attrs, R.styleable.EditingKeyView).apply {
label = getString(R.styleable.EditingKeyView_android_text)
recycle()
}
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
themeManager.registerOnThemeUpdatedListener(this)
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
themeManager.unregisterOnThemeUpdatedListener(this)
}
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent?): Boolean {
if (!isEnabled || event == null) {
@@ -100,23 +120,20 @@ class EditingKeyView : AppCompatImageButton {
KeyCode.ARROW_RIGHT,
KeyCode.ARROW_UP,
KeyCode.DELETE -> {
osTimer = Timer()
osTimer?.scheduleAtFixedRate(object : TimerTask() {
override fun run() {
val delayMillis = prefs.keyboard.longPressDelay.toLong()
repeatedKeyPressHandler.postAtScheduledRate(delayMillis, 25) {
if (isKeyPressed) {
florisboard?.textInputManager?.sendKeyPress(data)
if (!isKeyPressed) {
osTimer?.cancel()
osTimer = null
}
} else {
repeatedKeyPressHandler.cancelAll()
}
}, 500, 50)
}
}
}
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
isKeyPressed = false
osTimer?.cancel()
osTimer = null
repeatedKeyPressHandler.cancelAll()
if (event.actionMasked != MotionEvent.ACTION_CANCEL) {
florisboard?.textInputManager?.sendKeyPress(data)
}
@@ -126,6 +143,16 @@ class EditingKeyView : AppCompatImageButton {
return true
}
override fun onThemeUpdated(theme: Theme) {
imageTintList = ColorStateList.valueOf(when {
isEnabled -> theme.getAttr(Theme.Attr.SMARTBAR_FOREGROUND).toSolidColor().color
else -> theme.getAttr(Theme.Attr.SMARTBAR_FOREGROUND_ALT).toSolidColor().color
})
colorHighlightedEnabled = theme.getAttr(Theme.Attr.WINDOW_COLOR_PRIMARY)
colorEnabled = theme.getAttr(Theme.Attr.SMARTBAR_FOREGROUND_ALT)
colorDefault = theme.getAttr(Theme.Attr.SMARTBAR_FOREGROUND)
}
/**
* Draw the key label / drawable.
*/
@@ -134,20 +161,15 @@ class EditingKeyView : AppCompatImageButton {
canvas ?: return
imageTintList = ColorStateList.valueOf(when {
isEnabled -> prefs.theme.smartbarFgColor
else -> prefs.theme.smartbarFgColorAlt
})
// Draw label
val label = label
if (label != null) {
labelPaint.color = if (isHighlighted && isEnabled) {
prefs.theme.colorPrimary
colorHighlightedEnabled.toSolidColor().color
} else if (!isEnabled) {
prefs.theme.smartbarFgColorAlt
colorEnabled.toSolidColor().color
} else {
prefs.theme.smartbarFgColor
colorDefault.toSolidColor().color
}
val isPortrait =
resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT

Some files were not shown because too many files have changed in this diff Show More