Compare commits

...

5 Commits

Author SHA1 Message Date
Jules Aguillon
191f5923e3 Fix overflow due to navigation on the right and cutouts
Take display cutouts and navigation bars when computing the keyboard
width on SDK >= 30.
2024-05-23 23:22:24 +02:00
NACAMURA Mitsuhiro
96fc4003f1 Update NixOS wiki link (#633) 2024-05-12 10:53:04 +02:00
Quinn Cypher
a91332a903 Pull the emoji list from unicode.org (#612)
- Removing unused information (names and descriptions) from the Emoji class
- Creating a Gradle task that generates a more efficient res/raw/emojis.txt file from the most recent Unicode standard
- Saving recently used emoji preferences as emoji values rather than names
- Migrating old user preferences to the new system
2024-05-08 13:02:19 +02:00
Spike
53e04d5784 Compass-point synonyms for edge keys in layouts (#628) 2024-05-08 12:51:11 +02:00
alotbsol555
c7d33356bc Add settings button to launcher app (#629) 2024-05-05 11:22:34 +02:00
12 changed files with 4702 additions and 3884 deletions

View File

@@ -14,7 +14,7 @@ Python 3 is required to update generated files but not to build the app.
For Android Studio users, no more setup is needed.
For Nix users, the right environment can be obtained with `nix-shell ./shell.nix`.
Instructions to install Nix are [here](https://nixos.wiki/wiki/Nix_Installation_Guide).
Instructions to install Nix are [here](https://wiki.nixos.org/wiki/Nix_Installation_Guide).
If you don't use Android Studio or Nix, you have to inform Gradle about the
location of your Android SDK by either:

View File

@@ -103,6 +103,14 @@ tasks.register('buildKeyboardFont') {
}
}
tasks.register('genEmojis') {
println "\nGenerating res/raw/emojis.txt"
exec {
workingDir = projectDir
commandLine "python", "gen_emoji.py"
}
}
tasks.withType(Test).configureEach {
dependsOn 'genLayoutsList'
dependsOn 'checkKeyboardLayouts'

38
gen_emoji.py Normal file
View File

@@ -0,0 +1,38 @@
import urllib.request
import os.path
EMOJIS_PATH = 'res/raw/emojis.txt'
EMOJI_TEST_PATH = 'emoji-test.txt'
EMOJI_TEST_URL = 'https://unicode.org/Public/emoji/latest/emoji-test.txt'
def rawEmojiFromCodes(codes):
return ''.join([chr(int(c, 16)) for c in codes])
def getEmojiTestContents():
if os.path.exists(EMOJI_TEST_PATH):
print(f'Using existing {EMOJI_TEST_PATH}')
else:
print(f'Downloading {EMOJI_TEST_URL}')
urllib.request.urlretrieve(EMOJI_TEST_URL, EMOJI_TEST_PATH)
return open(EMOJI_TEST_PATH, mode='r', encoding='UTF-8').read()
emoji_list = []
group_indices = []
for line in getEmojiTestContents().splitlines():
if line.startswith('# group:'):
if len(group_indices) == 0 or len(emoji_list) > group_indices[-1]:
group_indices.append(len(emoji_list))
elif not line.startswith('#') and 'fully-qualified' in line:
codes = line.split(';')[0].split()
emoji_list.append(rawEmojiFromCodes(codes))
with open(EMOJIS_PATH, 'w', encoding='UTF-8') as emojis:
for e in emoji_list:
emojis.write(f'{e}\n')
emojis.write('\n')
emojis.write(' '.join([str(g) for g in group_indices]))
emojis.write('\n')
print(f'Parsed {len(emoji_list)} emojis in {len(group_indices)}')

View File

@@ -0,0 +1 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24"><path android:fillColor="#999999" android:pathData="M12,8A4,4 0 0,1 16,12A4,4 0 0,1 12,16A4,4 0 0,1 8,12A4,4 0 0,1 12,8M12,10A2,2 0 0,0 10,12A2,2 0 0,0 12,14A2,2 0 0,0 14,12A2,2 0 0,0 12,10M10,22C9.75,22 9.54,21.82 9.5,21.58L9.13,18.93C8.5,18.68 7.96,18.34 7.44,17.94L4.95,18.95C4.73,19.03 4.46,18.95 4.34,18.73L2.34,15.27C2.21,15.05 2.27,14.78 2.46,14.63L4.57,12.97L4.5,12L4.57,11L2.46,9.37C2.27,9.22 2.21,8.95 2.34,8.73L4.34,5.27C4.46,5.05 4.73,4.96 4.95,5.05L7.44,6.05C7.96,5.66 8.5,5.32 9.13,5.07L9.5,2.42C9.54,2.18 9.75,2 10,2H14C14.25,2 14.46,2.18 14.5,2.42L14.87,5.07C15.5,5.32 16.04,5.66 16.56,6.05L19.05,5.05C19.27,4.96 19.54,5.05 19.66,5.27L21.66,8.73C21.79,8.95 21.73,9.22 21.54,9.37L19.43,11L19.5,12L19.43,13L21.54,14.63C21.73,14.78 21.79,15.05 21.66,15.27L19.66,18.73C19.54,18.95 19.27,19.04 19.05,18.95L16.56,17.95C16.04,18.34 15.5,18.68 14.87,18.93L14.5,21.58C14.46,21.82 14.25,22 14,22H10M11.25,4L10.88,6.61C9.68,6.86 8.62,7.5 7.85,8.39L5.44,7.35L4.69,8.65L6.8,10.2C6.4,11.37 6.4,12.64 6.8,13.8L4.68,15.36L5.43,16.66L7.86,15.62C8.63,16.5 9.68,17.14 10.87,17.38L11.24,20H12.76L13.13,17.39C14.32,17.14 15.37,16.5 16.14,15.62L18.57,16.66L19.32,15.36L17.2,13.81C17.6,12.64 17.6,11.37 17.2,10.2L19.31,8.65L18.56,7.35L16.15,8.39C15.38,7.5 14.32,6.86 13.12,6.62L12.75,4H11.25Z" /></vector>

View File

@@ -0,0 +1,10 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".LauncherActivity">
<item
android:id="@+id/btnLaunchSettingsActivity"
android:title=""
android:icon="@drawable/cog_outline"
android:showAsAction="always" />
</menu>

File diff suppressed because it is too large Load Diff

View File

@@ -7,26 +7,15 @@ import java.io.IOException;
import java.io.BufferedReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class Emoji
{
private final String _name;
private final KeyValue _kv;
private final String _desc;
private static HashMap<String, Emoji> emojis_by_name = new HashMap<String, Emoji>();
protected Emoji(String name, String bytecode, String desc)
protected Emoji(String bytecode)
{
_name = name;
_kv = new KeyValue(bytecode, KeyValue.Kind.String, 0, 0);
_desc = desc;
emojis_by_name.put(name, this);
}
public String name()
{
return _name;
this._kv = new KeyValue(bytecode, KeyValue.Kind.String, 0, 0);
}
public KeyValue kv()
@@ -34,54 +23,766 @@ public class Emoji
return _kv;
}
public String getDescription()
{
return (_desc);
}
public static int num_groups = 0;
private final static List<Emoji> _all = new ArrayList<>();
private final static List<List<Emoji>> _groups = new ArrayList<>();
private final static HashMap<String, Emoji> _stringMap = new HashMap<>();
private static Emoji[][] emojis_by_group = new Emoji[][]{};
public static Emoji getEmojiByName(String name)
{
return emojis_by_name.get(name);
}
public static Emoji[] getEmojisByGroup(int group_id)
{
return (emojis_by_group[group_id]);
}
/* Read the list of emojis from a raw file. Will initialize only once. */
public static void init(Resources res)
{
if (num_groups > 0)
if (!_all.isEmpty())
return;
try
{
ArrayList<Emoji[]> groups = new ArrayList<Emoji[]>();
InputStream inputStream = res.openRawResource(R.raw.emojis);
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
String line;
while (true)
// Read emoji (until empty line)
while (!(line = reader.readLine()).isEmpty())
{
line = reader.readLine();
if (line == null)
break;
int group_len = Integer.parseInt(line);
Emoji[] grp = new Emoji[group_len];
for (int i = 0; i < group_len; i++)
{
line = reader.readLine();
String[] f = line.split(" ", 3);
grp[i] = new Emoji(f[0], f[1], f[2]);
}
groups.add(grp);
Emoji e = new Emoji(line);
_all.add(e);
_stringMap.put(line, e);
}
// Read group indices
if ((line = reader.readLine()) != null)
{
String[] tokens = line.split(" ");
for (int i = 0; i < tokens.length-1; i++)
_groups.add(_all.subList(Integer.parseInt(tokens[i]), Integer.parseInt(tokens[i+1])));
}
num_groups = groups.size();
emojis_by_group = groups.toArray(new Emoji[0][]);
}
catch (IOException e) {}
catch (IOException e) { Logs.exn("Emoji.init() failed", e); }
}
public static int getNumGroups()
{
return _groups.size();
}
public static List<Emoji> getEmojisByGroup(int groupIndex)
{
return _groups.get(groupIndex);
}
public static Emoji getEmojiByString(String value)
{
return _stringMap.get(value);
}
public static String mapOldNameToValue(String name) throws IllegalArgumentException
{
if (name.matches(":(u[a-fA-F0-9]{4,5})+:"))
{
StringBuilder sb = new StringBuilder();
for (String code : name.replace(":", "").substring(1).split("u"))
{
try
{
sb.append(Character.toChars(Integer.decode("0X" + code)));
}
catch (IllegalArgumentException e)
{
throw new IllegalArgumentException("Failed to parse codepoint '" + code + "' in name '" + name + "'", e);
}
}
return sb.toString();
}
switch (name)
{
case ":grinning:": return "😀";
case ":smiley:": return "😃";
case ":smile:": return "😄";
case ":grin:": return "😁";
case ":satisfied:": return "😆";
case ":sweat_smile:": return "😅";
case ":joy:": return "😂";
case ":wink:": return "😉";
case ":blush:": return "😊";
case ":innocent:": return "😇";
case ":heart_eyes:": return "😍";
case ":kissing_heart:": return "😘";
case ":kissing:": return "😗";
case ":kissing_closed_eyes:": return "😚";
case ":kissing_smiling_eyes:": return "😙";
case ":yum:": return "😋";
case ":stuck_out_tongue:": return "😛";
case ":stuck_out_tongue_winking_eye:": return "😜";
case ":stuck_out_tongue_closed_eyes:": return "😝";
case ":neutral_face:": return "😐";
case ":expressionless:": return "😑";
case ":no_mouth:": return "😶";
case ":smirk:": return "😏";
case ":unamused:": return "😒";
case ":grimacing:": return "😬";
case ":relieved:": return "😌";
case ":pensive:": return "😔";
case ":sleepy:": return "😪";
case ":sleeping:": return "😴";
case ":mask:": return "😷";
case ":dizzy_face:": return "😵";
case ":sunglasses:": return "😎";
case ":confused:": return "😕";
case ":worried:": return "😟";
case ":open_mouth:": return "😮";
case ":hushed:": return "😯";
case ":astonished:": return "😲";
case ":flushed:": return "😳";
case ":frowning:": return "😦";
case ":anguished:": return "😧";
case ":fearful:": return "😨";
case ":cold_sweat:": return "😰";
case ":disappointed_relieved:": return "😥";
case ":cry:": return "😢";
case ":sob:": return "😭";
case ":scream:": return "😱";
case ":confounded:": return "😖";
case ":persevere:": return "😣";
case ":disappointed:": return "😞";
case ":sweat:": return "😓";
case ":weary:": return "😩";
case ":tired_face:": return "😫";
case ":triumph:": return "😤";
case ":rage:": return "😡";
case ":angry:": return "😠";
case ":smiling_imp:": return "😈";
case ":imp:": return "👿";
case ":skull:": return "💀";
case ":shit:": return "💩";
case ":japanese_ogre:": return "👹";
case ":japanese_goblin:": return "👺";
case ":ghost:": return "👻";
case ":alien:": return "👽";
case ":space_invader:": return "👾";
case ":smiley_cat:": return "😺";
case ":smile_cat:": return "😸";
case ":joy_cat:": return "😹";
case ":heart_eyes_cat:": return "😻";
case ":smirk_cat:": return "😼";
case ":kissing_cat:": return "😽";
case ":scream_cat:": return "🙀";
case ":crying_cat_face:": return "😿";
case ":pouting_cat:": return "😾";
case ":see_no_evil:": return "🙈";
case ":hear_no_evil:": return "🙉";
case ":speak_no_evil:": return "🙊";
case ":kiss:": return "💋";
case ":love_letter:": return "💌";
case ":cupid:": return "💘";
case ":gift_heart:": return "💝";
case ":sparkling_heart:": return "💖";
case ":heartpulse:": return "💗";
case ":heartbeat:": return "💓";
case ":revolving_hearts:": return "💞";
case ":two_hearts:": return "💕";
case ":heart_decoration:": return "💟";
case ":broken_heart:": return "💔";
case ":yellow_heart:": return "💛";
case ":green_heart:": return "💚";
case ":blue_heart:": return "💙";
case ":purple_heart:": return "💜";
case ":100:": return "💯";
case ":anger:": return "💢";
case ":collision:": return "💥";
case ":dizzy:": return "💫";
case ":sweat_drops:": return "💦";
case ":dash:": return "💨";
case ":bomb:": return "💣";
case ":speech_balloon:": return "💬";
case ":thought_balloon:": return "💭";
case ":zzz:": return "💤";
case ":wave:": return "👋";
case ":ok_hand:": return "👌";
case ":point_left:": return "👈";
case ":point_right:": return "👉";
case ":point_up_2:": return "👆";
case ":point_down:": return "👇";
case ":thumbsup:": return "👍";
case ":thumbsdown:": return "👎";
case ":punch:": return "👊";
case ":clap:": return "👏";
case ":raised_hands:": return "🙌";
case ":open_hands:": return "👐";
case ":pray:": return "🙏";
case ":nail_care:": return "💅";
case ":muscle:": return "💪";
case ":ear:": return "👂";
case ":nose:": return "👃";
case ":eyes:": return "👀";
case ":tongue:": return "👅";
case ":lips:": return "👄";
case ":baby:": return "👶";
case ":boy:": return "👦";
case ":girl:": return "👧";
case ":person_with_blond_hair:": return "👱";
case ":man:": return "👨";
case ":woman:": return "👩";
case ":older_man:": return "👴";
case ":older_woman:": return "👵";
case ":person_frowning:": return "🙍";
case ":person_with_pouting_face:": return "🙎";
case ":no_good:": return "🙅";
case ":ok_woman:": return "🙆";
case ":information_desk_person:": return "💁";
case ":raising_hand:": return "🙋";
case ":bow:": return "🙇";
case ":cop:": return "👮";
case ":guardsman:": return "💂";
case ":construction_worker:": return "👷";
case ":princess:": return "👸";
case ":man_with_turban:": return "👳";
case ":man_with_gua_pi_mao:": return "👲";
case ":bride_with_veil:": return "👰";
case ":angel:": return "👼";
case ":santa:": return "🎅";
case ":massage:": return "💆";
case ":haircut:": return "💇";
case ":walking:": return "🚶";
case ":running:": return "🏃";
case ":dancer:": return "💃";
case ":dancers:": return "👯";
case ":horse_racing:": return "🏇";
case ":snowboarder:": return "🏂";
case ":surfer:": return "🏄";
case ":rowboat:": return "🚣";
case ":swimmer:": return "🏊";
case ":bicyclist:": return "🚴";
case ":mountain_bicyclist:": return "🚵";
case ":bath:": return "🛀";
case ":two_women_holding_hands:": return "👭";
case ":couple:": return "👫";
case ":two_men_holding_hands:": return "👬";
case ":couplekiss:": return "💏";
case ":couple_with_heart:": return "💑";
case ":family:": return "👪";
case ":bust_in_silhouette:": return "👤";
case ":busts_in_silhouette:": return "👥";
case ":footprints:": return "👣";
case ":monkey_face:": return "🐵";
case ":monkey:": return "🐒";
case ":dog:": return "🐶";
case ":dog2:": return "🐕";
case ":poodle:": return "🐩";
case ":wolf:": return "🐺";
case ":cat:": return "🐱";
case ":cat2:": return "🐈";
case ":tiger:": return "🐯";
case ":tiger2:": return "🐅";
case ":leopard:": return "🐆";
case ":horse:": return "🐴";
case ":racehorse:": return "🐎";
case ":cow:": return "🐮";
case ":ox:": return "🐂";
case ":water_buffalo:": return "🐃";
case ":cow2:": return "🐄";
case ":pig:": return "🐷";
case ":pig2:": return "🐖";
case ":boar:": return "🐗";
case ":pig_nose:": return "🐽";
case ":ram:": return "🐏";
case ":sheep:": return "🐑";
case ":goat:": return "🐐";
case ":dromedary_camel:": return "🐪";
case ":camel:": return "🐫";
case ":elephant:": return "🐘";
case ":mouse:": return "🐭";
case ":mouse2:": return "🐁";
case ":rat:": return "🐀";
case ":hamster:": return "🐹";
case ":rabbit:": return "🐰";
case ":rabbit2:": return "🐇";
case ":bear:": return "🐻";
case ":koala:": return "🐨";
case ":panda_face:": return "🐼";
case ":paw_prints:": return "🐾";
case ":chicken:": return "🐔";
case ":rooster:": return "🐓";
case ":hatching_chick:": return "🐣";
case ":baby_chick:": return "🐤";
case ":hatched_chick:": return "🐥";
case ":bird:": return "🐦";
case ":penguin:": return "🐧";
case ":frog:": return "🐸";
case ":crocodile:": return "🐊";
case ":turtle:": return "🐢";
case ":snake:": return "🐍";
case ":dragon_face:": return "🐲";
case ":dragon:": return "🐉";
case ":whale:": return "🐳";
case ":whale2:": return "🐋";
case ":flipper:": return "🐬";
case ":fish:": return "🐟";
case ":tropical_fish:": return "🐠";
case ":blowfish:": return "🐡";
case ":octopus:": return "🐙";
case ":shell:": return "🐚";
case ":snail:": return "🐌";
case ":bug:": return "🐛";
case ":ant:": return "🐜";
case ":honeybee:": return "🐝";
case ":beetle:": return "🐞";
case ":bouquet:": return "💐";
case ":cherry_blossom:": return "🌸";
case ":white_flower:": return "💮";
case ":rose:": return "🌹";
case ":hibiscus:": return "🌺";
case ":sunflower:": return "🌻";
case ":blossom:": return "🌼";
case ":tulip:": return "🌷";
case ":seedling:": return "🌱";
case ":evergreen_tree:": return "🌲";
case ":deciduous_tree:": return "🌳";
case ":palm_tree:": return "🌴";
case ":cactus:": return "🌵";
case ":ear_of_rice:": return "🌾";
case ":herb:": return "🌿";
case ":four_leaf_clover:": return "🍀";
case ":maple_leaf:": return "🍁";
case ":fallen_leaf:": return "🍂";
case ":leaves:": return "🍃";
case ":grapes:": return "🍇";
case ":melon:": return "🍈";
case ":watermelon:": return "🍉";
case ":tangerine:": return "🍊";
case ":lemon:": return "🍋";
case ":banana:": return "🍌";
case ":pineapple:": return "🍍";
case ":apple:": return "🍎";
case ":green_apple:": return "🍏";
case ":pear:": return "🍐";
case ":peach:": return "🍑";
case ":cherries:": return "🍒";
case ":strawberry:": return "🍓";
case ":tomato:": return "🍅";
case ":eggplant:": return "🍆";
case ":corn:": return "🌽";
case ":mushroom:": return "🍄";
case ":chestnut:": return "🌰";
case ":bread:": return "🍞";
case ":meat_on_bone:": return "🍖";
case ":poultry_leg:": return "🍗";
case ":hamburger:": return "🍔";
case ":fries:": return "🍟";
case ":pizza:": return "🍕";
case ":egg:": return "🍳";
case ":stew:": return "🍲";
case ":bento:": return "🍱";
case ":rice_cracker:": return "🍘";
case ":rice_ball:": return "🍙";
case ":rice:": return "🍚";
case ":curry:": return "🍛";
case ":ramen:": return "🍜";
case ":spaghetti:": return "🍝";
case ":sweet_potato:": return "🍠";
case ":oden:": return "🍢";
case ":sushi:": return "🍣";
case ":fried_shrimp:": return "🍤";
case ":fish_cake:": return "🍥";
case ":dango:": return "🍡";
case ":icecream:": return "🍦";
case ":shaved_ice:": return "🍧";
case ":ice_cream:": return "🍨";
case ":doughnut:": return "🍩";
case ":cookie:": return "🍪";
case ":birthday:": return "🎂";
case ":cake:": return "🍰";
case ":chocolate_bar:": return "🍫";
case ":candy:": return "🍬";
case ":lollipop:": return "🍭";
case ":custard:": return "🍮";
case ":honey_pot:": return "🍯";
case ":baby_bottle:": return "🍼";
case ":tea:": return "🍵";
case ":sake:": return "🍶";
case ":wine_glass:": return "🍷";
case ":cocktail:": return "🍸";
case ":tropical_drink:": return "🍹";
case ":beer:": return "🍺";
case ":beers:": return "🍻";
case ":fork_and_knife:": return "🍴";
case ":hocho:": return "🔪";
case ":earth_africa:": return "🌍";
case ":earth_americas:": return "🌎";
case ":earth_asia:": return "🌏";
case ":globe_with_meridians:": return "🌐";
case ":japan:": return "🗾";
case ":volcano:": return "🌋";
case ":mount_fuji:": return "🗻";
case ":house:": return "🏠";
case ":house_with_garden:": return "🏡";
case ":office:": return "🏢";
case ":post_office:": return "🏣";
case ":european_post_office:": return "🏤";
case ":hospital:": return "🏥";
case ":bank:": return "🏦";
case ":hotel:": return "🏨";
case ":love_hotel:": return "🏩";
case ":convenience_store:": return "🏪";
case ":school:": return "🏫";
case ":department_store:": return "🏬";
case ":factory:": return "🏭";
case ":japanese_castle:": return "🏯";
case ":european_castle:": return "🏰";
case ":wedding:": return "💒";
case ":tokyo_tower:": return "🗼";
case ":statue_of_liberty:": return "🗽";
case ":foggy:": return "🌁";
case ":stars:": return "🌃";
case ":sunrise_over_mountains:": return "🌄";
case ":sunrise:": return "🌅";
case ":city_sunset:": return "🌆";
case ":city_sunrise:": return "🌇";
case ":bridge_at_night:": return "🌉";
case ":carousel_horse:": return "🎠";
case ":ferris_wheel:": return "🎡";
case ":roller_coaster:": return "🎢";
case ":barber:": return "💈";
case ":circus_tent:": return "🎪";
case ":steam_locomotive:": return "🚂";
case ":train:": return "🚃";
case ":bullettrain_side:": return "🚄";
case ":bullettrain_front:": return "🚅";
case ":train2:": return "🚆";
case ":metro:": return "🚇";
case ":light_rail:": return "🚈";
case ":station:": return "🚉";
case ":tram:": return "🚊";
case ":monorail:": return "🚝";
case ":mountain_railway:": return "🚞";
case ":bus:": return "🚌";
case ":oncoming_bus:": return "🚍";
case ":trolleybus:": return "🚎";
case ":minibus:": return "🚐";
case ":ambulance:": return "🚑";
case ":fire_engine:": return "🚒";
case ":police_car:": return "🚓";
case ":oncoming_police_car:": return "🚔";
case ":taxi:": return "🚕";
case ":oncoming_taxi:": return "🚖";
case ":red_car:": return "🚗";
case ":oncoming_automobile:": return "🚘";
case ":blue_car:": return "🚙";
case ":truck:": return "🚚";
case ":articulated_lorry:": return "🚛";
case ":tractor:": return "🚜";
case ":bike:": return "🚲";
case ":busstop:": return "🚏";
case ":rotating_light:": return "🚨";
case ":traffic_light:": return "🚥";
case ":vertical_traffic_light:": return "🚦";
case ":construction:": return "🚧";
case ":speedboat:": return "🚤";
case ":ship:": return "🚢";
case ":seat:": return "💺";
case ":helicopter:": return "🚁";
case ":suspension_railway:": return "🚟";
case ":mountain_cableway:": return "🚠";
case ":aerial_tramway:": return "🚡";
case ":rocket:": return "🚀";
case ":clock12:": return "🕛";
case ":clock1230:": return "🕧";
case ":clock1:": return "🕐";
case ":clock130:": return "🕜";
case ":clock2:": return "🕑";
case ":clock230:": return "🕝";
case ":clock3:": return "🕒";
case ":clock330:": return "🕞";
case ":clock4:": return "🕓";
case ":clock430:": return "🕟";
case ":clock5:": return "🕔";
case ":clock530:": return "🕠";
case ":clock6:": return "🕕";
case ":clock630:": return "🕡";
case ":clock7:": return "🕖";
case ":clock730:": return "🕢";
case ":clock8:": return "🕗";
case ":clock830:": return "🕣";
case ":clock9:": return "🕘";
case ":clock930:": return "🕤";
case ":clock10:": return "🕙";
case ":clock1030:": return "🕥";
case ":clock11:": return "🕚";
case ":clock1130:": return "🕦";
case ":new_moon:": return "🌑";
case ":waxing_crescent_moon:": return "🌒";
case ":first_quarter_moon:": return "🌓";
case ":waxing_gibbous_moon:": return "🌔";
case ":full_moon:": return "🌕";
case ":waning_gibbous_moon:": return "🌖";
case ":last_quarter_moon:": return "🌗";
case ":waning_crescent_moon:": return "🌘";
case ":crescent_moon:": return "🌙";
case ":new_moon_with_face:": return "🌚";
case ":first_quarter_moon_with_face:": return "🌛";
case ":last_quarter_moon_with_face:": return "🌜";
case ":full_moon_with_face:": return "🌝";
case ":sun_with_face:": return "🌞";
case ":star2:": return "🌟";
case ":milky_way:": return "🌌";
case ":cyclone:": return "🌀";
case ":rainbow:": return "🌈";
case ":closed_umbrella:": return "🌂";
case ":fire:": return "🔥";
case ":droplet:": return "💧";
case ":ocean:": return "🌊";
case ":jack_o_lantern:": return "🎃";
case ":christmas_tree:": return "🎄";
case ":fireworks:": return "🎆";
case ":sparkler:": return "🎇";
case ":balloon:": return "🎈";
case ":tada:": return "🎉";
case ":confetti_ball:": return "🎊";
case ":tanabata_tree:": return "🎋";
case ":bamboo:": return "🎍";
case ":dolls:": return "🎎";
case ":flags:": return "🎏";
case ":wind_chime:": return "🎐";
case ":rice_scene:": return "🎑";
case ":ribbon:": return "🎀";
case ":gift:": return "🎁";
case ":ticket:": return "🎫";
case ":trophy:": return "🏆";
case ":basketball:": return "🏀";
case ":football:": return "🏈";
case ":rugby_football:": return "🏉";
case ":tennis:": return "🎾";
case ":bowling:": return "🎳";
case ":fishing_pole_and_fish:": return "🎣";
case ":running_shirt_with_sash:": return "🎽";
case ":ski:": return "🎿";
case ":dart:": return "🎯";
case ":8ball:": return "🎱";
case ":crystal_ball:": return "🔮";
case ":video_game:": return "🎮";
case ":slot_machine:": return "🎰";
case ":game_die:": return "🎲";
case ":black_joker:": return "🃏";
case ":mahjong:": return "🀄";
case ":flower_playing_cards:": return "🎴";
case ":performing_arts:": return "🎭";
case ":art:": return "🎨";
case ":eyeglasses:": return "👓";
case ":necktie:": return "👔";
case ":tshirt:": return "👕";
case ":jeans:": return "👖";
case ":dress:": return "👗";
case ":kimono:": return "👘";
case ":bikini:": return "👙";
case ":womans_clothes:": return "👚";
case ":purse:": return "👛";
case ":handbag:": return "👜";
case ":pouch:": return "👝";
case ":school_satchel:": return "🎒";
case ":shoe:": return "👞";
case ":athletic_shoe:": return "👟";
case ":high_heel:": return "👠";
case ":sandal:": return "👡";
case ":boot:": return "👢";
case ":crown:": return "👑";
case ":womans_hat:": return "👒";
case ":tophat:": return "🎩";
case ":mortar_board:": return "🎓";
case ":lipstick:": return "💄";
case ":ring:": return "💍";
case ":gem:": return "💎";
case ":mute:": return "🔇";
case ":sound:": return "🔉";
case ":speaker:": return "🔊";
case ":loudspeaker:": return "📢";
case ":mega:": return "📣";
case ":postal_horn:": return "📯";
case ":bell:": return "🔔";
case ":no_bell:": return "🔕";
case ":musical_score:": return "🎼";
case ":musical_note:": return "🎵";
case ":notes:": return "🎶";
case ":microphone:": return "🎤";
case ":headphones:": return "🎧";
case ":radio:": return "📻";
case ":saxophone:": return "🎷";
case ":guitar:": return "🎸";
case ":musical_keyboard:": return "🎹";
case ":trumpet:": return "🎺";
case ":violin:": return "🎻";
case ":iphone:": return "📱";
case ":calling:": return "📲";
case ":telephone_receiver:": return "📞";
case ":pager:": return "📟";
case ":fax:": return "📠";
case ":battery:": return "🔋";
case ":electric_plug:": return "🔌";
case ":computer:": return "💻";
case ":minidisc:": return "💽";
case ":floppy_disk:": return "💾";
case ":cd:": return "💿";
case ":dvd:": return "📀";
case ":movie_camera:": return "🎥";
case ":clapper:": return "🎬";
case ":tv:": return "📺";
case ":camera:": return "📷";
case ":video_camera:": return "📹";
case ":vhs:": return "📼";
case ":mag:": return "🔍";
case ":mag_right:": return "🔎";
case ":bulb:": return "💡";
case ":flashlight:": return "🔦";
case ":lantern:": return "🏮";
case ":notebook_with_decorative_cover:": return "📔";
case ":closed_book:": return "📕";
case ":open_book:": return "📖";
case ":green_book:": return "📗";
case ":blue_book:": return "📘";
case ":orange_book:": return "📙";
case ":books:": return "📚";
case ":notebook:": return "📓";
case ":ledger:": return "📒";
case ":page_with_curl:": return "📃";
case ":scroll:": return "📜";
case ":page_facing_up:": return "📄";
case ":newspaper:": return "📰";
case ":bookmark_tabs:": return "📑";
case ":bookmark:": return "🔖";
case ":moneybag:": return "💰";
case ":yen:": return "💴";
case ":dollar:": return "💵";
case ":euro:": return "💶";
case ":pound:": return "💷";
case ":money_with_wings:": return "💸";
case ":credit_card:": return "💳";
case ":chart:": return "💹";
case ":e-mail:": return "📧";
case ":incoming_envelope:": return "📨";
case ":envelope_with_arrow:": return "📩";
case ":outbox_tray:": return "📤";
case ":inbox_tray:": return "📥";
case ":package:": return "📦";
case ":mailbox:": return "📫";
case ":mailbox_closed:": return "📪";
case ":mailbox_with_mail:": return "📬";
case ":mailbox_with_no_mail:": return "📭";
case ":postbox:": return "📮";
case ":pencil:": return "📝";
case ":briefcase:": return "💼";
case ":file_folder:": return "📁";
case ":open_file_folder:": return "📂";
case ":date:": return "📅";
case ":calendar:": return "📆";
case ":card_index:": return "📇";
case ":chart_with_upwards_trend:": return "📈";
case ":chart_with_downwards_trend:": return "📉";
case ":bar_chart:": return "📊";
case ":clipboard:": return "📋";
case ":pushpin:": return "📌";
case ":round_pushpin:": return "📍";
case ":paperclip:": return "📎";
case ":straight_ruler:": return "📏";
case ":triangular_ruler:": return "📐";
case ":lock:": return "🔒";
case ":lock_with_ink_pen:": return "🔏";
case ":closed_lock_with_key:": return "🔐";
case ":key:": return "🔑";
case ":hammer:": return "🔨";
case ":gun:": return "🔫";
case ":wrench:": return "🔧";
case ":nut_and_bolt:": return "🔩";
case ":link:": return "🔗";
case ":microscope:": return "🔬";
case ":telescope:": return "🔭";
case ":satellite:": return "📡";
case ":syringe:": return "💉";
case ":pill:": return "💊";
case ":door:": return "🚪";
case ":toilet:": return "🚽";
case ":shower:": return "🚿";
case ":bathtub:": return "🛁";
case ":smoking:": return "🚬";
case ":moyai:": return "🗿";
case ":atm:": return "🏧";
case ":put_litter_in_its_place:": return "🚮";
case ":potable_water:": return "🚰";
case ":mens:": return "🚹";
case ":womens:": return "🚺";
case ":restroom:": return "🚻";
case ":baby_symbol:": return "🚼";
case ":wc:": return "🚾";
case ":passport_control:": return "🛂";
case ":customs:": return "🛃";
case ":baggage_claim:": return "🛄";
case ":left_luggage:": return "🛅";
case ":children_crossing:": return "🚸";
case ":no_entry_sign:": return "🚫";
case ":no_bicycles:": return "🚳";
case ":no_smoking:": return "🚭";
case ":do_not_litter:": return "🚯";
case ":non-potable_water:": return "🚱";
case ":no_pedestrians:": return "🚷";
case ":no_mobile_phones:": return "📵";
case ":underage:": return "🔞";
case ":arrows_clockwise:": return "🔃";
case ":arrows_counterclockwise:": return "🔄";
case ":back:": return "🔙";
case ":end:": return "🔚";
case ":on:": return "🔛";
case ":soon:": return "🔜";
case ":top:": return "🔝";
case ":six_pointed_star:": return "🔯";
case ":twisted_rightwards_arrows:": return "🔀";
case ":repeat:": return "🔁";
case ":repeat_one:": return "🔂";
case ":arrow_up_small:": return "🔼";
case ":arrow_down_small:": return "🔽";
case ":cinema:": return "🎦";
case ":low_brightness:": return "🔅";
case ":high_brightness:": return "🔆";
case ":signal_strength:": return "📶";
case ":vibration_mode:": return "📳";
case ":mobile_phone_off:": return "📴";
case ":currency_exchange:": return "💱";
case ":heavy_dollar_sign:": return "💲";
case ":trident:": return "🔱";
case ":name_badge:": return "📛";
case ":beginner:": return "🔰";
case ":keycap_ten:": return "🔟";
case ":capital_abcd:": return "🔠";
case ":abcd:": return "🔡";
case ":1234:": return "🔢";
case ":symbols:": return "🔣";
case ":abc:": return "🔤";
case ":ab:": return "🆎";
case ":cl:": return "🆑";
case ":cool:": return "🆒";
case ":free:": return "🆓";
case ":id:": return "🆔";
case ":new:": return "🆕";
case ":ng:": return "🆖";
case ":ok:": return "🆗";
case ":sos:": return "🆘";
case ":up:": return "🆙";
case ":vs:": return "🆚";
case ":koko:": return "🈁";
case ":ideograph_advantage:": return "🉐";
case ":accept:": return "🉑";
case ":red_circle:": return "🔴";
case ":large_blue_circle:": return "🔵";
case ":large_orange_diamond:": return "🔶";
case ":large_blue_diamond:": return "🔷";
case ":small_orange_diamond:": return "🔸";
case ":small_blue_diamond:": return "🔹";
case ":small_red_triangle:": return "🔺";
case ":small_red_triangle_down:": return "🔻";
case ":diamond_shape_with_a_dot_inside:": return "💠";
case ":radio_button:": return "🔘";
case ":white_square_button:": return "🔳";
case ":black_square_button:": return "🔲";
case ":checkered_flag:": return "🏁";
case ":triangular_flag_on_post:": return "🚩";
case ":crossed_flags:": return "🎌";
}
throw new IllegalArgumentException("'" + name + "' is not a valid name");
}
}

View File

@@ -10,10 +10,12 @@ import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.GridView;
import android.widget.TextView;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class EmojiGridView extends GridView
@@ -23,7 +25,7 @@ public class EmojiGridView extends GridView
private static final String LAST_USE_PREF = "emoji_last_use";
private Emoji[] _emojiArray;
private List<Emoji> _emojiArray;
private HashMap<Emoji, Integer> _lastUsed;
/*
@@ -34,6 +36,7 @@ public class EmojiGridView extends GridView
{
super(context, attrs);
Emoji.init(context.getResources());
migrateOldPrefs(); // TODO: Remove at some point in future
setOnItemClickListener(this);
loadLastUsed();
setEmojiGroup((_lastUsed.size() == 0) ? 0 : GROUP_LAST_USE);
@@ -48,26 +51,23 @@ public class EmojiGridView extends GridView
public void onItemClick(AdapterView<?> parent, View v, int pos, long id)
{
Config config = Config.globalConfig();
Integer used = _lastUsed.get(_emojiArray[pos]);
_lastUsed.put(_emojiArray[pos], (used == null) ? 1 : used.intValue() + 1);
config.handler.key_up(_emojiArray[pos].kv(), Pointers.Modifiers.EMPTY);
Integer used = _lastUsed.get(_emojiArray.get(pos));
_lastUsed.put(_emojiArray.get(pos), (used == null) ? 1 : used.intValue() + 1);
config.handler.key_up(_emojiArray.get(pos).kv(), Pointers.Modifiers.EMPTY);
saveLastUsed(); // TODO: opti
}
private Emoji[] getLastEmojis()
private List<Emoji> getLastEmojis()
{
final HashMap<Emoji, Integer> map = _lastUsed;
Emoji[] array = new Emoji[map.size()];
map.keySet().toArray(array);
Arrays.sort(array, 0, array.length, new Comparator<Emoji>()
List<Emoji> list = new ArrayList<>(_lastUsed.keySet());
Collections.sort(list, new Comparator<Emoji>()
{
public int compare(Emoji a, Emoji b)
{
return (map.get(b).intValue() - map.get(a).intValue());
return _lastUsed.get(b) - _lastUsed.get(a);
}
});
return (array);
return list;
}
private void saveLastUsed()
@@ -77,7 +77,7 @@ public class EmojiGridView extends GridView
catch (Exception _e) { return; }
HashSet<String> set = new HashSet<String>();
for (Emoji emoji : _lastUsed.keySet())
set.add(String.valueOf(_lastUsed.get(emoji)) + "-" + emoji.name());
set.add(String.valueOf(_lastUsed.get(emoji)) + "-" + emoji.kv().getString());
edit.putStringSet(LAST_USE_PREF, set);
edit.apply();
}
@@ -98,7 +98,7 @@ public class EmojiGridView extends GridView
Emoji emoji;
if (data.length != 2)
continue ;
emoji = Emoji.getEmojiByName(data[1]);
emoji = Emoji.getEmojiByString(data[1]);
if (emoji == null)
continue ;
_lastUsed.put(emoji, Integer.valueOf(data[0]));
@@ -110,6 +110,37 @@ public class EmojiGridView extends GridView
return getContext().getSharedPreferences("emoji_last_use", Context.MODE_PRIVATE);
}
private void migrateOldPrefs()
{
final String MIGRATION_CHECK_KEY = "MIGRATION_COMPLETE";
SharedPreferences prefs;
try { prefs = emojiSharedPreferences(); }
catch (Exception e) { return; }
Set<String> lastUsed = prefs.getStringSet(LAST_USE_PREF, null);
if (lastUsed != null && !prefs.getBoolean(MIGRATION_CHECK_KEY, false))
{
SharedPreferences.Editor edit = prefs.edit();
edit.clear();
Set<String> lastUsedNew = new HashSet<>();
for (String entry : lastUsed)
{
String[] data = entry.split("-", 2);
try
{
lastUsedNew.add(Integer.parseInt(data[0]) + "-" + Emoji.mapOldNameToValue(data[1]));
}
catch (IllegalArgumentException ignored) {}
}
edit.putStringSet(LAST_USE_PREF, lastUsedNew);
edit.putBoolean(MIGRATION_CHECK_KEY, true);
edit.apply();
}
}
static class EmojiView extends TextView
{
public EmojiView(Context context)
@@ -127,9 +158,9 @@ public class EmojiGridView extends GridView
{
Context _button_context;
Emoji[] _emojiArray;
List<Emoji> _emojiArray;
public EmojiViewAdpater(Context context, Emoji[] emojiArray)
public EmojiViewAdpater(Context context, List<Emoji> emojiArray)
{
_button_context = new ContextThemeWrapper(context, R.style.emojiGridButton);
_emojiArray = emojiArray;
@@ -139,12 +170,12 @@ public class EmojiGridView extends GridView
{
if (_emojiArray == null)
return (0);
return (_emojiArray.length);
return (_emojiArray.size());
}
public Object getItem(int pos)
{
return (_emojiArray[pos]);
return (_emojiArray.get(pos));
}
public long getItemId(int pos)
@@ -158,7 +189,7 @@ public class EmojiGridView extends GridView
if (view == null)
view = new EmojiView(_button_context);
view.setEmoji(_emojiArray[pos]);
view.setEmoji(_emojiArray.get(pos));
return view;
}
}

View File

@@ -19,9 +19,9 @@ public class EmojiGroupButtonsBar extends LinearLayout
super(context, attrs);
Emoji.init(context.getResources());
add_group(EmojiGridView.GROUP_LAST_USE, "\uD83D\uDD59");
for (int i = 0; i < Emoji.num_groups; i++)
for (int i = 0; i < Emoji.getNumGroups(); i++)
{
Emoji first = Emoji.getEmojisByGroup(i)[0];
Emoji first = Emoji.getEmojisByGroup(i).get(0);
add_group(i, first.kv().getString());
}
}

View File

@@ -3,6 +3,7 @@ package juloo.keyboard2;
import android.content.Context;
import android.content.ContextWrapper;
import android.graphics.Canvas;
import android.graphics.Insets;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
@@ -13,6 +14,9 @@ import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowMetrics;
import java.util.Arrays;
public class Keyboard2View extends View
@@ -256,6 +260,17 @@ public class Keyboard2View extends View
int height =
(int)(_config.keyHeight * _keyboard.keysHeight
+ _config.marginTop + _config.margin_bottom);
// Compatibility with display cutouts and navigation on the right
if (VERSION.SDK_INT >= 30)
{
WindowMetrics metrics =
((WindowManager)getContext().getSystemService(Context.WINDOW_SERVICE))
.getCurrentWindowMetrics();
Insets insets = metrics.getWindowInsets().getInsetsIgnoringVisibility(
WindowInsets.Type.statusBars() | WindowInsets.Type.navigationBars()
| WindowInsets.Type.displayCutout());
width = metrics.getBounds().width() - insets.right - insets.left;
}
setMeasuredDimension(width, height);
_keyWidth = (width - (_config.horizontal_margin * 2)) / _keyboard.keysWidth;
}

View File

@@ -399,24 +399,37 @@ public final class KeyboardData
indication = i;
}
/** Write the parsed key into [ks] at [index]. Doesn't write if the
attribute is not present. Return flags that can be aggregated into the
value for [keysflags]. */
static int parse_key_attr(XmlPullParser parser, String attr, KeyValue[] ks,
/** Read a key value attribute that have a synonym. Having both synonyms
present at the same time is an error.
Returns [null] if the attributes are not present. */
static String get_key_attr(XmlPullParser parser, String syn1, String syn2)
throws Exception
{
String name1 = parser.getAttributeValue(null, syn1);
String name2 = parser.getAttributeValue(null, syn2);
if (name1 != null && name2 != null)
throw error(parser,
"'"+syn1+"' and '"+syn2+"' are synonyms and cannot be passed at the same time.");
return (name1 == null) ? name2 : name1;
}
/** Parse the key description [key_attr] and write into [ks] at [index].
Returns flags that can be aggregated into the value for [keysflags].
[key_attr] can be [null] for convenience. */
static int parse_key_attr(XmlPullParser parser, String key_val, KeyValue[] ks,
int index)
throws Exception
{
String name = parser.getAttributeValue(null, attr);
int flags = 0;
if (name == null)
if (key_val == null)
return 0;
String name_loc = stripPrefix(name, "loc ");
int flags = 0;
String name_loc = stripPrefix(key_val, "loc ");
if (name_loc != null)
{
flags |= F_LOC;
name = name_loc;
key_val = name_loc;
}
ks[index] = KeyValue.getKeyByName(name);
ks[index] = KeyValue.getKeyByName(key_val);
return (flags << index);
}
@@ -429,15 +442,17 @@ public final class KeyboardData
{
KeyValue[] ks = new KeyValue[9];
int keysflags = 0;
keysflags |= parse_key_attr(parser, "key0", ks, 0);
keysflags |= parse_key_attr(parser, "key1", ks, 1);
keysflags |= parse_key_attr(parser, "key2", ks, 2);
keysflags |= parse_key_attr(parser, "key3", ks, 3);
keysflags |= parse_key_attr(parser, "key4", ks, 4);
keysflags |= parse_key_attr(parser, "key5", ks, 5);
keysflags |= parse_key_attr(parser, "key6", ks, 6);
keysflags |= parse_key_attr(parser, "key7", ks, 7);
keysflags |= parse_key_attr(parser, "key8", ks, 8);
keysflags |= parse_key_attr(parser, parser.getAttributeValue(null, "key0"), ks, 0);
/* Swipe gestures (key1-key8 diagram above), with compass-point synonyms. */
keysflags |= parse_key_attr(parser, get_key_attr(parser, "key1", "nw"), ks, 1);
keysflags |= parse_key_attr(parser, get_key_attr(parser, "key2", "ne"), ks, 2);
keysflags |= parse_key_attr(parser, get_key_attr(parser, "key3", "sw"), ks, 3);
keysflags |= parse_key_attr(parser, get_key_attr(parser, "key4", "se"), ks, 4);
keysflags |= parse_key_attr(parser, get_key_attr(parser, "key5", "w"), ks, 5);
keysflags |= parse_key_attr(parser, get_key_attr(parser, "key6", "e"), ks, 6);
keysflags |= parse_key_attr(parser, get_key_attr(parser, "key7", "n"), ks, 7);
keysflags |= parse_key_attr(parser, get_key_attr(parser, "key8", "s"), ks, 8);
/* Other key attributes */
float width = attribute_float(parser, "width", 1f);
float shift = attribute_float(parser, "shift", 0.f);
boolean slider = attribute_bool(parser, "slider", false);

View File

@@ -10,6 +10,8 @@ import android.os.Build.VERSION;
import android.os.Bundle;
import android.provider.Settings;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
@@ -37,7 +39,21 @@ public class LauncherActivity extends Activity
this.new Tryhere_OnUnhandledKeyEventListener());
setup_intro_video(_intro_video);
}
@Override
public final boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.launcher_menu, menu);
return true;
}
@Override
public final boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.btnLaunchSettingsActivity) {
Intent intent = new Intent(LauncherActivity.this, SettingsActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
return super.onOptionsItemSelected(item);
}
public void launch_imesettings(View _btn)
{
startActivity(new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS));