diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml index b268ef3..3bf9052 100644 --- a/.idea/deploymentTargetSelector.xml +++ b/.idea/deploymentTargetSelector.xml @@ -4,6 +4,22 @@ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0ced4fa..b836c59 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,8 +3,8 @@ - - + + @@ -30,22 +31,26 @@ @@ -63,5 +68,16 @@ android:name="android.view.im" android:resource="@xml/method" /> + + + + + diff --git a/app/src/main/assets/bi_cols.bin b/app/src/main/assets/bi_cols.bin index 3394ced..f34f23e 100644 Binary files a/app/src/main/assets/bi_cols.bin and b/app/src/main/assets/bi_cols.bin differ diff --git a/app/src/main/assets/bi_logp.bin b/app/src/main/assets/bi_logp.bin index 1a76e70..4b71d60 100644 Binary files a/app/src/main/assets/bi_logp.bin and b/app/src/main/assets/bi_logp.bin differ diff --git a/app/src/main/assets/bi_rowptr.bin b/app/src/main/assets/bi_rowptr.bin index 113e6e7..288cef2 100644 Binary files a/app/src/main/assets/bi_rowptr.bin and b/app/src/main/assets/bi_rowptr.bin differ diff --git a/app/src/main/assets/keyboard_themes/default/Key_collapse.png b/app/src/main/assets/keyboard_themes/default/Key_collapse.png index 6696970..68d5c27 100644 Binary files a/app/src/main/assets/keyboard_themes/default/Key_collapse.png and b/app/src/main/assets/keyboard_themes/default/Key_collapse.png differ diff --git a/app/src/main/assets/keyboard_themes/default/background.png b/app/src/main/assets/keyboard_themes/default/background.png index c2a690d..d750125 100644 Binary files a/app/src/main/assets/keyboard_themes/default/background.png and b/app/src/main/assets/keyboard_themes/default/background.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_0.png b/app/src/main/assets/keyboard_themes/default/key_0.png index 3f50ba7..35a70ea 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_0.png and b/app/src/main/assets/keyboard_themes/default/key_0.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_1.png b/app/src/main/assets/keyboard_themes/default/key_1.png index a63ddbe..fcb29f1 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_1.png and b/app/src/main/assets/keyboard_themes/default/key_1.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_123.png b/app/src/main/assets/keyboard_themes/default/key_123.png index 2742137..cc45ff2 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_123.png and b/app/src/main/assets/keyboard_themes/default/key_123.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_2.png b/app/src/main/assets/keyboard_themes/default/key_2.png index dbc74b9..bdfcf3b 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_2.png and b/app/src/main/assets/keyboard_themes/default/key_2.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_3.png b/app/src/main/assets/keyboard_themes/default/key_3.png index f38b8a3..1dc9644 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_3.png and b/app/src/main/assets/keyboard_themes/default/key_3.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_4.png b/app/src/main/assets/keyboard_themes/default/key_4.png index ce0566b..e474d11 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_4.png and b/app/src/main/assets/keyboard_themes/default/key_4.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_5.png b/app/src/main/assets/keyboard_themes/default/key_5.png index f1aecfe..ba2f53d 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_5.png and b/app/src/main/assets/keyboard_themes/default/key_5.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_6.png b/app/src/main/assets/keyboard_themes/default/key_6.png index c76f7ac..3bc78a6 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_6.png and b/app/src/main/assets/keyboard_themes/default/key_6.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_7.png b/app/src/main/assets/keyboard_themes/default/key_7.png index 20be3d8..df9bfe9 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_7.png and b/app/src/main/assets/keyboard_themes/default/key_7.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_8.png b/app/src/main/assets/keyboard_themes/default/key_8.png index 6ee3dfc..018207a 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_8.png and b/app/src/main/assets/keyboard_themes/default/key_8.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_9.png b/app/src/main/assets/keyboard_themes/default/key_9.png index 3459117..8b9a922 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_9.png and b/app/src/main/assets/keyboard_themes/default/key_9.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_a.png b/app/src/main/assets/keyboard_themes/default/key_a.png index 76687e3..42e98b9 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_a.png and b/app/src/main/assets/keyboard_themes/default/key_a.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_a_up.png b/app/src/main/assets/keyboard_themes/default/key_a_up.png index 2598472..049a9a2 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_a_up.png and b/app/src/main/assets/keyboard_themes/default/key_a_up.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_abc.png b/app/src/main/assets/keyboard_themes/default/key_abc.png index a1923a6..e0424ad 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_abc.png and b/app/src/main/assets/keyboard_themes/default/key_abc.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_ai.png b/app/src/main/assets/keyboard_themes/default/key_ai.png index e3cf382..b9812d7 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_ai.png and b/app/src/main/assets/keyboard_themes/default/key_ai.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_amp.png b/app/src/main/assets/keyboard_themes/default/key_amp.png index 03538fa..264d14f 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_amp.png and b/app/src/main/assets/keyboard_themes/default/key_amp.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_asterisk.png b/app/src/main/assets/keyboard_themes/default/key_asterisk.png index 620ce9f..6986979 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_asterisk.png and b/app/src/main/assets/keyboard_themes/default/key_asterisk.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_at.png b/app/src/main/assets/keyboard_themes/default/key_at.png index 00ca5ed..de261f9 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_at.png and b/app/src/main/assets/keyboard_themes/default/key_at.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_b.png b/app/src/main/assets/keyboard_themes/default/key_b.png index ef6e890..d13aa06 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_b.png and b/app/src/main/assets/keyboard_themes/default/key_b.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_b_up.png b/app/src/main/assets/keyboard_themes/default/key_b_up.png index bd5a787..8f470d3 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_b_up.png and b/app/src/main/assets/keyboard_themes/default/key_b_up.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_backslash.png b/app/src/main/assets/keyboard_themes/default/key_backslash.png index aa8563c..a97e362 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_backslash.png and b/app/src/main/assets/keyboard_themes/default/key_backslash.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_backspace.png b/app/src/main/assets/keyboard_themes/default/key_backspace.png deleted file mode 100644 index 129f1af..0000000 Binary files a/app/src/main/assets/keyboard_themes/default/key_backspace.png and /dev/null differ diff --git a/app/src/main/assets/keyboard_themes/default/key_brace_l.png b/app/src/main/assets/keyboard_themes/default/key_brace_l.png index 5c28b8a..9338dbf 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_brace_l.png and b/app/src/main/assets/keyboard_themes/default/key_brace_l.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_brace_r.png b/app/src/main/assets/keyboard_themes/default/key_brace_r.png index b1b2eb1..fb585c8 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_brace_r.png and b/app/src/main/assets/keyboard_themes/default/key_brace_r.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_bracket_l.png b/app/src/main/assets/keyboard_themes/default/key_bracket_l.png index 1ec5367..dbc4d05 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_bracket_l.png and b/app/src/main/assets/keyboard_themes/default/key_bracket_l.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_bracket_r.png b/app/src/main/assets/keyboard_themes/default/key_bracket_r.png index 300523d..fa633be 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_bracket_r.png and b/app/src/main/assets/keyboard_themes/default/key_bracket_r.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_bullet.png b/app/src/main/assets/keyboard_themes/default/key_bullet.png index 3ac5c87..5a9549d 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_bullet.png and b/app/src/main/assets/keyboard_themes/default/key_bullet.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_c.png b/app/src/main/assets/keyboard_themes/default/key_c.png index 63fe12c..998425b 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_c.png and b/app/src/main/assets/keyboard_themes/default/key_c.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_c_up.png b/app/src/main/assets/keyboard_themes/default/key_c_up.png index d823d4f..95f2581 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_c_up.png and b/app/src/main/assets/keyboard_themes/default/key_c_up.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_caret.png b/app/src/main/assets/keyboard_themes/default/key_caret.png index 073533a..a7d055f 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_caret.png and b/app/src/main/assets/keyboard_themes/default/key_caret.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_colon.png b/app/src/main/assets/keyboard_themes/default/key_colon.png index 83069a9..07004af 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_colon.png and b/app/src/main/assets/keyboard_themes/default/key_colon.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_comma.png b/app/src/main/assets/keyboard_themes/default/key_comma.png index 641c7d6..89e8b0f 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_comma.png and b/app/src/main/assets/keyboard_themes/default/key_comma.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_d.png b/app/src/main/assets/keyboard_themes/default/key_d.png index ec4aac5..73e5f2d 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_d.png and b/app/src/main/assets/keyboard_themes/default/key_d.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_d_up.png b/app/src/main/assets/keyboard_themes/default/key_d_up.png index 79188a0..fe70bd9 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_d_up.png and b/app/src/main/assets/keyboard_themes/default/key_d_up.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_del.png b/app/src/main/assets/keyboard_themes/default/key_del.png index 129f1af..adbf8f7 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_del.png and b/app/src/main/assets/keyboard_themes/default/key_del.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_dollar.png b/app/src/main/assets/keyboard_themes/default/key_dollar.png index f1ae8b9..e06c81f 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_dollar.png and b/app/src/main/assets/keyboard_themes/default/key_dollar.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_dot.png b/app/src/main/assets/keyboard_themes/default/key_dot.png index adb7bd2..f26e595 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_dot.png and b/app/src/main/assets/keyboard_themes/default/key_dot.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_e.png b/app/src/main/assets/keyboard_themes/default/key_e.png index 1266671..22fa162 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_e.png and b/app/src/main/assets/keyboard_themes/default/key_e.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_e_up.png b/app/src/main/assets/keyboard_themes/default/key_e_up.png index b7ea305..cdc5fcb 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_e_up.png and b/app/src/main/assets/keyboard_themes/default/key_e_up.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_emoji.png b/app/src/main/assets/keyboard_themes/default/key_emoji.png new file mode 100644 index 0000000..299ac42 Binary files /dev/null and b/app/src/main/assets/keyboard_themes/default/key_emoji.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_equal.png b/app/src/main/assets/keyboard_themes/default/key_equal.png index a6df242..fee04e4 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_equal.png and b/app/src/main/assets/keyboard_themes/default/key_equal.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_euro.png b/app/src/main/assets/keyboard_themes/default/key_euro.png index dd2a727..bc34dd7 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_euro.png and b/app/src/main/assets/keyboard_themes/default/key_euro.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_exclam.png b/app/src/main/assets/keyboard_themes/default/key_exclam.png index 9b13fe9..24a6266 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_exclam.png and b/app/src/main/assets/keyboard_themes/default/key_exclam.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_f.png b/app/src/main/assets/keyboard_themes/default/key_f.png index bb41a4b..6be8401 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_f.png and b/app/src/main/assets/keyboard_themes/default/key_f.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_f_up.png b/app/src/main/assets/keyboard_themes/default/key_f_up.png index 613b05c..8916620 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_f_up.png and b/app/src/main/assets/keyboard_themes/default/key_f_up.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_g.png b/app/src/main/assets/keyboard_themes/default/key_g.png index 1e2023b..586b06e 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_g.png and b/app/src/main/assets/keyboard_themes/default/key_g.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_g_up.png b/app/src/main/assets/keyboard_themes/default/key_g_up.png index 3885062..2fe71a8 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_g_up.png and b/app/src/main/assets/keyboard_themes/default/key_g_up.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_gt.png b/app/src/main/assets/keyboard_themes/default/key_gt.png index 6aab0fb..cc10e4d 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_gt.png and b/app/src/main/assets/keyboard_themes/default/key_gt.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_h.png b/app/src/main/assets/keyboard_themes/default/key_h.png index 97cc234..90076a4 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_h.png and b/app/src/main/assets/keyboard_themes/default/key_h.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_h_up.png b/app/src/main/assets/keyboard_themes/default/key_h_up.png index 169b11c..ba77f87 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_h_up.png and b/app/src/main/assets/keyboard_themes/default/key_h_up.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_hash.png b/app/src/main/assets/keyboard_themes/default/key_hash.png index 2dc2f72..14a1ed1 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_hash.png and b/app/src/main/assets/keyboard_themes/default/key_hash.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_i.png b/app/src/main/assets/keyboard_themes/default/key_i.png index 77cded8..609bccb 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_i.png and b/app/src/main/assets/keyboard_themes/default/key_i.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_i_up.png b/app/src/main/assets/keyboard_themes/default/key_i_up.png index c44dd63..4335b19 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_i_up.png and b/app/src/main/assets/keyboard_themes/default/key_i_up.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_j.png b/app/src/main/assets/keyboard_themes/default/key_j.png index cf66ae5..c99d93e 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_j.png and b/app/src/main/assets/keyboard_themes/default/key_j.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_j_up.png b/app/src/main/assets/keyboard_themes/default/key_j_up.png index 15221e9..cc535a2 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_j_up.png and b/app/src/main/assets/keyboard_themes/default/key_j_up.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_k.png b/app/src/main/assets/keyboard_themes/default/key_k.png index 4c802e8..35d8693 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_k.png and b/app/src/main/assets/keyboard_themes/default/key_k.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_k_up.png b/app/src/main/assets/keyboard_themes/default/key_k_up.png index 18b5ef2..2d60615 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_k_up.png and b/app/src/main/assets/keyboard_themes/default/key_k_up.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_l.png b/app/src/main/assets/keyboard_themes/default/key_l.png index 3da5812..a3a95d6 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_l.png and b/app/src/main/assets/keyboard_themes/default/key_l.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_l_up.png b/app/src/main/assets/keyboard_themes/default/key_l_up.png index 48d11fe..f85cbd5 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_l_up.png and b/app/src/main/assets/keyboard_themes/default/key_l_up.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_lt.png b/app/src/main/assets/keyboard_themes/default/key_lt.png index 8094090..c97de96 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_lt.png and b/app/src/main/assets/keyboard_themes/default/key_lt.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_m.png b/app/src/main/assets/keyboard_themes/default/key_m.png index 6b5af2c..7d9e583 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_m.png and b/app/src/main/assets/keyboard_themes/default/key_m.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_m_up.png b/app/src/main/assets/keyboard_themes/default/key_m_up.png index b84d37c..4838732 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_m_up.png and b/app/src/main/assets/keyboard_themes/default/key_m_up.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_minus.png b/app/src/main/assets/keyboard_themes/default/key_minus.png index c1912a3..1d16de5 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_minus.png and b/app/src/main/assets/keyboard_themes/default/key_minus.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_money.png b/app/src/main/assets/keyboard_themes/default/key_money.png index 42220fe..d2c1d67 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_money.png and b/app/src/main/assets/keyboard_themes/default/key_money.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_n.png b/app/src/main/assets/keyboard_themes/default/key_n.png index dab1f87..33f9cae 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_n.png and b/app/src/main/assets/keyboard_themes/default/key_n.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_n_up.png b/app/src/main/assets/keyboard_themes/default/key_n_up.png index 1d88286..2cc14b3 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_n_up.png and b/app/src/main/assets/keyboard_themes/default/key_n_up.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_o.png b/app/src/main/assets/keyboard_themes/default/key_o.png index 23cf2b9..c3e4a5b 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_o.png and b/app/src/main/assets/keyboard_themes/default/key_o.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_o_up.png b/app/src/main/assets/keyboard_themes/default/key_o_up.png index c3231f9..6c26507 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_o_up.png and b/app/src/main/assets/keyboard_themes/default/key_o_up.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_p.png b/app/src/main/assets/keyboard_themes/default/key_p.png index e93161b..a76ab9d 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_p.png and b/app/src/main/assets/keyboard_themes/default/key_p.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_p_up.png b/app/src/main/assets/keyboard_themes/default/key_p_up.png index 70194e2..cbeb0db 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_p_up.png and b/app/src/main/assets/keyboard_themes/default/key_p_up.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_paren_l.png b/app/src/main/assets/keyboard_themes/default/key_paren_l.png index 36f3555..7fc553a 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_paren_l.png and b/app/src/main/assets/keyboard_themes/default/key_paren_l.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_paren_r.png b/app/src/main/assets/keyboard_themes/default/key_paren_r.png index 6ce9365..e4bbda1 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_paren_r.png and b/app/src/main/assets/keyboard_themes/default/key_paren_r.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_percent.png b/app/src/main/assets/keyboard_themes/default/key_percent.png index a7943e2..aa5f800 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_percent.png and b/app/src/main/assets/keyboard_themes/default/key_percent.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_pipe.png b/app/src/main/assets/keyboard_themes/default/key_pipe.png index 7025885..b089b4b 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_pipe.png and b/app/src/main/assets/keyboard_themes/default/key_pipe.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_plus.png b/app/src/main/assets/keyboard_themes/default/key_plus.png index 4257687..8616e59 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_plus.png and b/app/src/main/assets/keyboard_themes/default/key_plus.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_pound.png b/app/src/main/assets/keyboard_themes/default/key_pound.png index 9218ec7..bc2a560 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_pound.png and b/app/src/main/assets/keyboard_themes/default/key_pound.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_q.png b/app/src/main/assets/keyboard_themes/default/key_q.png index f19cf50..9bdf87e 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_q.png and b/app/src/main/assets/keyboard_themes/default/key_q.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_q_up.png b/app/src/main/assets/keyboard_themes/default/key_q_up.png index 5d4df5a..7ae4e97 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_q_up.png and b/app/src/main/assets/keyboard_themes/default/key_q_up.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_question.png b/app/src/main/assets/keyboard_themes/default/key_question.png index bd2de25..5648e74 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_question.png and b/app/src/main/assets/keyboard_themes/default/key_question.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_quote.png b/app/src/main/assets/keyboard_themes/default/key_quote.png index 9812639..357b3e3 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_quote.png and b/app/src/main/assets/keyboard_themes/default/key_quote.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_quote_d.png b/app/src/main/assets/keyboard_themes/default/key_quote_d.png index ad314c5..b238bc2 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_quote_d.png and b/app/src/main/assets/keyboard_themes/default/key_quote_d.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_r.png b/app/src/main/assets/keyboard_themes/default/key_r.png index 17eabf6..445c888 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_r.png and b/app/src/main/assets/keyboard_themes/default/key_r.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_r_up.png b/app/src/main/assets/keyboard_themes/default/key_r_up.png index 8566458..d43c37b 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_r_up.png and b/app/src/main/assets/keyboard_themes/default/key_r_up.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_revoke.png b/app/src/main/assets/keyboard_themes/default/key_revoke.png new file mode 100644 index 0000000..664827e Binary files /dev/null and b/app/src/main/assets/keyboard_themes/default/key_revoke.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_s.png b/app/src/main/assets/keyboard_themes/default/key_s.png index d585210..5024992 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_s.png and b/app/src/main/assets/keyboard_themes/default/key_s.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_s_up.png b/app/src/main/assets/keyboard_themes/default/key_s_up.png index 96f8e59..73ca4ca 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_s_up.png and b/app/src/main/assets/keyboard_themes/default/key_s_up.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_semicolon.png b/app/src/main/assets/keyboard_themes/default/key_semicolon.png index d154544..91c9351 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_semicolon.png and b/app/src/main/assets/keyboard_themes/default/key_semicolon.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_send.png b/app/src/main/assets/keyboard_themes/default/key_send.png index f15aae1..36ae642 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_send.png and b/app/src/main/assets/keyboard_themes/default/key_send.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_slash.png b/app/src/main/assets/keyboard_themes/default/key_slash.png index 8f28257..783deff 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_slash.png and b/app/src/main/assets/keyboard_themes/default/key_slash.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_space.png b/app/src/main/assets/keyboard_themes/default/key_space.png index 4db597c..7218309 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_space.png and b/app/src/main/assets/keyboard_themes/default/key_space.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_symbols_123.png b/app/src/main/assets/keyboard_themes/default/key_symbols_123.png index e2e9a5b..ab6dff5 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_symbols_123.png and b/app/src/main/assets/keyboard_themes/default/key_symbols_123.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_symbols_more.png b/app/src/main/assets/keyboard_themes/default/key_symbols_more.png index a5b40cd..c4eeb19 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_symbols_more.png and b/app/src/main/assets/keyboard_themes/default/key_symbols_more.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_t.png b/app/src/main/assets/keyboard_themes/default/key_t.png index f94a079..3038975 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_t.png and b/app/src/main/assets/keyboard_themes/default/key_t.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_t_up.png b/app/src/main/assets/keyboard_themes/default/key_t_up.png index 1bfba2a..5203e0a 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_t_up.png and b/app/src/main/assets/keyboard_themes/default/key_t_up.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_tilde.png b/app/src/main/assets/keyboard_themes/default/key_tilde.png index de61625..e7fdcac 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_tilde.png and b/app/src/main/assets/keyboard_themes/default/key_tilde.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_u.png b/app/src/main/assets/keyboard_themes/default/key_u.png index dc06718..c8e147a 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_u.png and b/app/src/main/assets/keyboard_themes/default/key_u.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_u_up.png b/app/src/main/assets/keyboard_themes/default/key_u_up.png index 1d2ba3c..8497786 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_u_up.png and b/app/src/main/assets/keyboard_themes/default/key_u_up.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_underscore.png b/app/src/main/assets/keyboard_themes/default/key_underscore.png index b88b39d..2f3bcf0 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_underscore.png and b/app/src/main/assets/keyboard_themes/default/key_underscore.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_up.png b/app/src/main/assets/keyboard_themes/default/key_up.png index 9b825cd..bfd31ed 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_up.png and b/app/src/main/assets/keyboard_themes/default/key_up.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_up_upper.png b/app/src/main/assets/keyboard_themes/default/key_up_upper.png index 52cd704..f902943 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_up_upper.png and b/app/src/main/assets/keyboard_themes/default/key_up_upper.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_v.png b/app/src/main/assets/keyboard_themes/default/key_v.png index b6dba48..c4bdb86 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_v.png and b/app/src/main/assets/keyboard_themes/default/key_v.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_v_up.png b/app/src/main/assets/keyboard_themes/default/key_v_up.png index a3026fa..eddc674 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_v_up.png and b/app/src/main/assets/keyboard_themes/default/key_v_up.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_w.png b/app/src/main/assets/keyboard_themes/default/key_w.png index ca77d4e..3146329 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_w.png and b/app/src/main/assets/keyboard_themes/default/key_w.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_w_up.png b/app/src/main/assets/keyboard_themes/default/key_w_up.png index 2f37d3b..788ce01 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_w_up.png and b/app/src/main/assets/keyboard_themes/default/key_w_up.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_x.png b/app/src/main/assets/keyboard_themes/default/key_x.png index baf8eec..439da77 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_x.png and b/app/src/main/assets/keyboard_themes/default/key_x.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_x_up.png b/app/src/main/assets/keyboard_themes/default/key_x_up.png index 72d9946..7c80335 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_x_up.png and b/app/src/main/assets/keyboard_themes/default/key_x_up.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_y.png b/app/src/main/assets/keyboard_themes/default/key_y.png index 3033c75..431575f 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_y.png and b/app/src/main/assets/keyboard_themes/default/key_y.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_y_up.png b/app/src/main/assets/keyboard_themes/default/key_y_up.png index 09bd13c..f39e85f 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_y_up.png and b/app/src/main/assets/keyboard_themes/default/key_y_up.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_z.png b/app/src/main/assets/keyboard_themes/default/key_z.png index e757c11..49e400c 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_z.png and b/app/src/main/assets/keyboard_themes/default/key_z.png differ diff --git a/app/src/main/assets/keyboard_themes/default/key_z_up.png b/app/src/main/assets/keyboard_themes/default/key_z_up.png index ae0b2b0..504d06a 100644 Binary files a/app/src/main/assets/keyboard_themes/default/key_z_up.png and b/app/src/main/assets/keyboard_themes/default/key_z_up.png differ diff --git a/app/src/main/assets/uni_logp.bin b/app/src/main/assets/uni_logp.bin index 8ad7338..4f838b8 100644 Binary files a/app/src/main/assets/uni_logp.bin and b/app/src/main/assets/uni_logp.bin differ diff --git a/app/src/main/assets/vocab.txt b/app/src/main/assets/vocab.txt index 7a7822a..450ecb2 100644 --- a/app/src/main/assets/vocab.txt +++ b/app/src/main/assets/vocab.txt @@ -49998,3 +49998,20759 @@ linga linsleyi listserv literati +litigations +locket +loincloth +loomed +loons +looseknit +lorica +louvers +lowdose +lowerranked +lowinterest +luminary +luteous +lyceum +macleayi +macrocephala +magazinestyle +magnolias +mailers +mailroom +mano +manumission +mapmaker +marinum +massifs +mata +matchlock +matchmakers +mauled +mbaqanga +mech +mediarelated +mediatised +medicalsurgical +meetups +megahit +melas +memorybased +mesangial +mesencephalon +meshed +mesopredator +messageoriented +metalloprotease +metonymy +metropolises +mezzo +microarchitectures +microcar +microtubuleassociated +midWales +middorsal +midprice +midwater +mie +miliaris +mindanaonis +minimallyinvasive +minis +minored +misadventure +misappropriating +misidentifications +mispronounced +misreading +mists +mitogenic +mn +mockingbird +modulations +moister +moisturizing +monetized +monosodium +muffin +muffler +mufti +multiarts +multinucleated +multiplexers +multipotent +multistoried +multisubunit +mush +muslims +mustread +mutinous +myoclonus +mystique +nadu +nakedeye +naloxone +narrowwaisted +nationalfederal +naturalborn +naturists +navarretia +ndrina +nearness +nearsightedness +needbased +needless +neocharismatic +netrin +neuroticism +neverending +nigropunctata +ninepiece +nomdeplume +nonIndigenous +nonOrthodox +nonagricultural +nonblocking +noncatalytic +noncompete +noncovalently +noncriminal +nonenveloped +nonfilm +nonfunctioning +nonidentical +nonnegative +nonorganic +nonrecourse +nonsporulating +nonsulfur +nontribal +nonviable +nosocomial +novelized +nucleases +nucleobase +nuraghe +nutritionists +obkom +obovate +obstinate +ocularis +oedema +offleash +oleander +ommatidia +oneeyed +onthespot +optouts +orchestrates +organismal +organochlorine +organofluorine +organum +orthocerids +osteosarcoma +outcompete +outdo +outflank +outofmarket +outpaced +outport +outsized +ov +overcharging +overdraft +overvoltage +overzealous +oviposit +ownerships +paclitaxel +paddlesteamer +padlocks +paisa +paleoconservative +pales +palika +pallidula +panfried +panregional +paraathlete +parareptiles +paresis +parolees +partygoers +passant +passionfruit +patientreported +patios +patrolman +paytelevision +pectineal +penitent +pentila +pepsin +pereiopods +perfectionist +periurban +perpetration +pervades +perverting +pew +phenological +phosphatidic +photobiont +phytochemical +phytosaurs +pierid +pignut +pillory +pinchhit +pinyon +pipa +piscina +pizzicato +plantpathogenic +plantsman +playmaking +pluralities +pluralityatlarge +podandboom +poetically +poi +pointsbased +policyoriented +pollinates +polychromatic +polysynthetic +pompom +pontiffs +ponytail +poop +popinfluenced +porthole +postWWII +postganglionic +postprimary +powertoweight +powertrains +pranksters +preapproved +preassembled +precollege +precooked +predicaments +prenylated +preordering +preowned +prepped +presidium +pretaped +pretournament +prettiest +principalis +proNazi +procrastination +producta +prof +projectionist +prophesies +prostigs +protostar +psephologist +pseudosuchian +pulldown +puma +punctum +purpleblack +purposedesigned +purpurata +pusillum +putida +pyridoxal +pyrrolizidine +quadriplegia +quarreling +queso +quilter +rackmount +raffia +railyard +rainstorms +rampaging +ranchs +raptorial +raytracing +readopted +reapportioned +reattached +rebut +receptorinteracting +reconquer +recused +redfaced +redlining +redoubts +redstriped +reductionist +reedit +reexamine +refillable +reformat +regius +regularized +rei +reining +relapses +releaser +remanufactured +remediate +remerged +remotes +repaint +resplendens +restatement +ret +rete +reticulation +retracts +retransmits +retrotransposons +rhabdomyolysis +rhizospheric +rhumba +ridding +ridiculing +rightbank +rightfully +rightmost +ringnecked +rishi +riverbeds +roadkill +roan +roasters +rockpower +rockpunk +rockstar +rolledleaf +rooks +rootstocks +rosinweed +roundups +rubberized +rudiments +rufousvented +rulesets +rumbling +rupturing +rutilus +sabers +saclike +saluted +sapient +sauropodomorph +savagery +scabbard +scalps +scalytoed +scarecrow +scariest +scions +sclerosing +scorch +screed +screensavers +scrofa +scutum +seasonality +secco +seconddeadliest +secondterm +sed +seethrough +sei +seine +seismological +selectmen +selfactualization +selfconsciously +selfcreated +selfdestruct +selfidentifying +selflaunching +selfpreservation +selfreflection +semiempirical +semihard +seminarys +semipublic +semisimple +sentimentality +separata +sepoys +ser +servos +sett +sevendigit +shanks +sharptailed +sheetmetal +shieldtail +shiners +shirtless +shockwave +shoemaking +shorn +shorttempered +signposts +silencers +silhouetted +silversmiths +simians +simulata +sinew +sirtuin +sive +sixdimensional +sixterm +sixtimes +slacks +slotting +slumber +slurries +smokebox +sneaked +sneeze +snobbish +snowmaking +snowman +soapy +sociality +socialnetworking +sockeye +solemnity +solidity +somaliensis +songstress +sorbitol +soundcheck +sourcecode +sparsa +spasmodic +spearfishing +spectrums +speedup +spherically +spinelike +splintering +spuria +stackbased +staid +standardgage +standpipe +staphylococcal +starlight +steelframe +steeplechasing +stepentrance +stickleback +stillexisting +stirs +stockbroking +storehouses +storia +strainer +strangler +streamside +streetlights +strenuously +strictness +strokeplay +structureactivity +stth +studentwritten +styrofoam +subarticle +subheading +subjectivism +subkingdom +subluxation +subscale +subscriptiononly +subventral +sudoku +sumatranus +superintendence +supermax +superstardom +suppers +surfacelevel +suspensethriller +swabs +swale +swish +sylvaticum +symbolist +syncytial +tacitly +tacky +tagteam +tailgating +tailorbird +takaful +takedowns +tantamount +tarda +tartans +technetium +techs +teleconferencing +telekinesis +telepath +tempeh +tempestuous +tenanted +tenax +tenpoint +tenthcentury +tenuicornis +terete +textilis +thenunreleased +thermoforming +thimble +thirdlongest +thirdly +thirdworld +thongs +thornbush +threenight +threeroom +thresher +tieup +timberline +timehonored +timeofflight +tipple +titlist +toasters +toenail +tokenization +tomahawk +toothbrushes +tossup +tote +totems +touchsensitive +trachomatis +transSaharan +transacting +transfected +transubstantiation +traversodontid +trematosaurian +triadic +triclinic +tripunctata +trophoblast +trustworthiness +trypanosomes +tuberosum +tumefaciens +tutu +tuya +tvOS +twelveinch +twentysomethings +twiggy +twinturboprop +twofer +twospeed +twospotted +typescript +typesetter +unabated +unbundled +undeciphered +underaged +undying +unencumbered +unflinching +unlobed +unpitched +uns +unspoken +unwillingly +uprooting +uptight +urbane +ustulata +uvula +vaccinia +vallenato +valved +vanduzeei +variegatum +varnished +vassalage +velutinus +venerate +verifiability +vestibulocochlear +vestitus +vexillology +vicariates +vicechampion +vihara +vilified +vill +vin +virally +visavis +viscounts +vitiligo +vitripennis +viziers +vocalizing +vulpes +vwo +wah +walkietalkie +walkup +wallichii +warthog +waterrelated +weathervane +wedgetailed +weep +wellfunded +wellhouse +wellliked +wellmade +wept +westernstyle +wharfs +whitecrested +whiteedged +whiteheadi +whitelabel +whitelined +whitenecked +wholebody +widowers +windstorms +wiretap +wistful +withering +woken +wolfpack +womenowned +workgroup +worthiness +wriggler +xxxx +yaks +yellowbrowed +yellowshouldered +yogic +zeitgeist +zelandica +zeroes +zeroth +zooid +zvezda +abnormis +abortifacient +abounded +abridgment +absconding +absurdities +academe +acquit +acrostic +acrylamide +actinocerids +actormanager +acuminate +adat +adderstongue +adjoint +adjourn +admirabilis +advertorial +aerobes +aerofoil +afarensis +affectivity +afire +agespecific +agonizing +agoraphobia +airside +alHadi +alKabir +albonotata +alcalde +alexandri +allIndia +allardi +allcomers +alleviates +alpacas +amplexicaulis +amyl +anaerobically +anaphylactic +angering +anglicisation +anhydrides +animates +animus +annihilate +annuum +anodic +ansorgei +anthophorine +anthracina +antiTaliban +anticoagulation +antifeminist +antihuman +antimoney +antiparkinsonian +antiphons +antipruritic +antivaccination +applicator +apposition +apprehensive +aprotic +archpriest +archways +aridus +arsenals +arthroplasty +arthroscopy +artiodactyls +artrelated +artrock +artsrelated +assemblys +astro +atomicity +attainments +attired +attractively +auctioning +auks +aurantius +aureola +auspice +austenitic +autoclave +averting +axing +ayahuasca +backhoe +bahiensis +ballplayers +balneario +bandicoots +bandshell +barbarism +basolateral +batesii +batswoman +baueri +baumannii +bayeri +begotten +belayer +belllike +bergfried +berlandieri +betaNacetylglucosaminyltransferase +bib +bichir +bidentatus +biflora +biguttatus +biochemists +bioinspired +biozone +biryani +bisecting +bispinosus +bitcoins +bitesized +bitterns +bitwise +blackchinned +blastula +blenders +bleu +blowfly +blueeye +bluespotted +bluntly +boletes +boneless +borohydride +bosons +bovid +boxings +branchs +brassiere +brownthroated +brucellosis +brushstrokes +buffalos +bulking +burgeoni +bused +buskers +byelaws +byword +cadences +caffer +cajun +calcaratus +calipers +caliphs +calisthenics +calliostoma +camelopardalis +canadien +candidly +candpolit +cantos +capabilitybased +capitalistic +capitate +captor +carabid +caramelized +carboniferous +carburettors +cargos +carne +carnifex +carpio +carpus +cartulary +catastrophically +catatonia +categorys +cattail +caucused +causally +celled +celltocell +centavos +centralism +centrifuges +cernua +cestodes +chalybeous +changers +chap +charmed +chasm +chatbot +chelate +chennai +cherish +childhoods +chile +chisels +chromaffin +chronobiology +churned +ciders +cilium +cingulatus +ciprofloxacin +circularly +circumvents +citizeninitiated +citizenships +citruses +classs +claustrophobic +clavigera +clubmosses +clubrooms +coax +coconsecrators +coerulescens +coeval +cognitions +collegiality +colonelcy +colonizer +colorings +comer +comforted +comicsrelated +commendam +commenters +communautaire +communitybuilding +compactus +complanata +complexed +comprehending +conational +conceptacles +concur +conformis +confuses +conicus +conidiophores +connexin +consecrating +consignee +consonance +contractility +conventual +convergens +convexity +cookoff +corbel +cordage +cordicollis +cordis +coriaceus +cornfield +coscreenwriter +costipennis +costulata +cottagers +coughs +counteracted +counterrotating +counterstain +courtesans +coverups +cowgirl +crablike +crassirostris +craterlike +crenellate +crockery +crony +crossreferencing +crowdfund +cryptically +cryptosystems +cyanipennis +cyathium +cystoscopy +cytochromes +dAssisi +dBase +dacoit +dado +dag +dahi +dama +damnation +dandruff +dandy +danieli +danse +daysofstatic +debased +debentures +debridement +decelerate +decile +decisionmaker +decked +decomposers +decouple +decrypting +deeplevel +defacement +defecating +defiantly +defibrillation +deftly +degeneracy +dehydratase +deification +demurrer +dephosphorylation +deportees +deputations +deputize +deserting +desiccated +dethrone +devastate +dextroamphetamine +dhrupad +diacritical +diagrammatic +dialectology +dicastery +dicta +digestible +digestsized +digitalis +diketone +dimple +dipper +directoral +disassemble +disintegrin +diskless +disklike +dismutase +disobeying +dispossession +dissents +dissipative +distancelearning +diterpenes +documentations +dollhouse +doorbell +doubletracked +doxycycline +dragline +draping +drenched +drudgery +drunkard +dsRNA +dualband +dualsided +dugesiid +dulce +durum +dwarfing +dysautonomia +dysmorphic +eXtensible +eburnea +echocardiogram +economys +editha +effaced +eigenvalues +eighthcentury +ejournal +electrodynamic +elegantissima +elongates +elongating +eluding +ember +embrasures +embroideries +emery +eminences +enchantment +encyclopedist +endeared +endocones +endophyte +endowing +enduros +enemas +energize +engelmannii +enslave +entrails +envelopment +ephemeris +epigraphical +epigraphist +epithelioid +epizootic +equina +erhu +eroge +erythrocephala +escapologist +eugenol +evaporite +excruciating +exculpatory +exempting +expediency +expending +expletives +explication +expound +extenuating +extorting +extrafloral +fainted +fairmairei +fallwinter +falsify +familia +fanbased +fastcasual +fatsoluble +feae +feedwater +feelgood +felted +feltlike +femorata +fenestrae +fenestratus +feng +ferritin +fervida +fess +fetishistic +ff +fibrinolysis +fictionadventure +fieldbased +fiestas +fifthseason +fiftyyear +filets +financials +firedamp +fivemile +fixeddose +fixedpoint +fixedprice +flashbased +flatheads +flaveola +flecked +flesheating +floccosa +flocculation +floodwater +fluidic +fluting +folkcountry +fontainei +forbs +forefather +forefinger +foregone +foreignpolicy +foreknowledge +foremast +foreshadows +forgave +forwardfacing +forwardthinking +foulmouthed +fourlegged +fourlined +fourtimes +fowleri +freelances +freemasonry +friaries +frontiersmen +frontmounted +fullblooded +funerea +furiously +fuscum +gallo +galvanize +ganglionic +garra +garret +gasholder +gatehouses +gen +genal +generale +geophysicists +geoscientist +geraniums +germinating +gforce +ghouls +ghrelin +glabrescens +glibc +glorifies +glucosidase +gmol +goalposts +goddaughter +goldenbacked +gondolas +gouramies +governmentappointed +grandiflorum +gratiosa +grayishgreen +greatgreatgranddaughter +greatgreatgreatgrandson +greyer +grisealis +guaranty +guardhouse +guerillas +guillemot +guitaroriented +gumbo +gymnosperm +hacklegilled +hafnium +hagiographic +haircare +hairyeyed +halfday +halfmoon +hallux +hardcorepunk +hardcovers +hardyhead +haustoria +hawthorns +headoffice +hearingimpaired +heartache +heartlung +heartthrob +heathen +hellebore +helmetshrike +helminths +hematuria +herdsmen +herpetologists +hesperus +heterotrophs +hexapods +hey +hiccups +highbudget +highestever +highfloor +highoctane +hijackings +hindlimb +hiplife +historiographic +homeotic +homevideo +homeware +hookups +hoopoe +hoprap +horrordrama +horseflies +hospitalacquired +hostnames +hugs +humanitarians +humile +humoristic +humors +humped +huttoni +hybrida +hybridus +hydratase +hydrogenated +hydrolyzing +hydroquinone +hydroxyapatite +hyperrealistic +hypersurface +hypomanic +hypoxantha +hyraxes +iSchool +iZombie +iberica +icefishes +ign +imipramine +impatiens +imperatives +impinging +implementers +incantations +incentivize +incongruent +indecision +indent +indict +indomitable +industrialscale +infringes +initiatory +innocently +inselbergs +institutionalize +insurrections +intensifier +interKorean +interconversion +interdistrict +interestfree +interferences +interlanguage +interlocutors +intermountain +internecine +internets +intifada +intima +intuitions +inversus +iota +irrationality +isolationism +isometry +isotonic +itemized +jacamar +jacketed +jae +jaguars +jazzpop +jigger +jonesii +jubatus +jugal +kanamycin +kernelbased +kilobases +kingfish +kiteboarding +kr +krater +kudu +labormanagement +lacquered +laevigatum +lagers +languid +largestselling +laryngitis +latitudinal +launchpad +leachate +leafeared +leaguewide +leitmotif +leonensis +leukemic +leukotrienes +levodopa +liars +lightbulb +lindae +lioness +lipases +liquified +litmus +littlealtered +livability +livebearing +loadcarrying +localism +localist +localitys +lonelygirl +longan +longbill +longfinned +longifolium +longline +longtoed +lores +lowangle +lowskilled +lumpy +lunettes +mabuya +magmas +magnesia +magnesian +magnocellular +mailings +makuuchi +maletofemale +manes +mangled +manis +mantids +marga +margaretae +marinade +marquetry +maudlin +maurus +mazelike +mealy +medialward +meditated +mediumterm +megakaryocytes +megalith +megalopolis +melaleucas +melanomas +meningoencephalitis +mensonly +mentalis +mercaptan +meristems +meromictic +merriami +mesonotum +messagepassing +mestizos +metalprogressive +metalworker +metopes +metrelong +meyeri +micrograms +micropayments +microprocessorbased +microsurgery +midteens +millimeterwave +millionseller +mindorensis +mindsets +mineralised +minicomic +minimax +minimi +minimums +mirei +miserly +mismatches +misrepresent +missal +misspellings +mixedused +mollissima +mona +monadic +monocle +mortician +mortification +mountainsides +mudbrick +multicenter +multidisciplined +multilobed +multiprotocol +multitap +multituberculate +multiweek +mumblecore +musae +myiasis +myxosporean +naga +naphthenic +nappy +natans +nativism +naturopathic +nearfield +neavei +needleshaped +neighbored +neurone +neutered +newscasters +newswire +ney +nibs +nigrifrons +nilotica +nilpotent +nociception +nolimit +nonAsian +nonactors +nonautonomous +noncirculating +noncooperative +nondominant +nongaming +nonindustrial +nonmetal +nonpaying +nonpenetrative +nonperiodic +nonpigmented +nonredundant +nonreproductive +nonsingle +nonsmallcell +nonsmoking +nonsporting +nonstarter +nonstick +northend +norvegica +nouvelle +nucleoplasm +numb +nunciature +nutcracker +nutritionally +nutshell +obiter +oceanographers +ochraceus +odorous +oesophageal +officinarum +offkey +offstreet +oilonpanel +olivebacked +oliveri +ora +orand +orexin +organosilicon +ornithomimosaurs +osteonecrosis +ostinato +outstation +overblown +overconfidence +overeating +overrunning +overtimes +overused +overwrite +ovina +oxeye +oxidants +packagers +paddocks +padic +pagodas +painstakingly +paleozoic +palpalis +pandemocrats +pantheons +paperandpencil +paralog +paralympics +parang +paratriathlete +pasteurization +pastorate +pastpresident +patricia +pauli +payam +pegging +penandink +penetrance +pentafluoride +perforata +perfumed +pericarp +peridotite +perimeters +peripatetic +perpetrating +personatus +perturbative +petiolaris +petiolata +petticoat +pharyngitis +phenomenally +phenotypical +phenotyping +phorbol +photoreconnaissance +photosharing +phrasal +pigmy +pilus +pinups +pitfall +pixilation +placeholders +plantigrade +plasticine +platinumcertified +platy +plausibly +playgroup +playmate +playset +pleiotropic +pleuralis +plucks +plumages +plumed +pluto +pochard +podcasters +policewomen +politely +polyA +polyamine +polyamines +polyandrous +polyesters +polyposis +portlet +portly +positrons +postconviction +postmistress +postsurgery +potty +precipice +predicative +prefab +prelaw +premeditation +presales +preth +prewritten +priapulid +pringlei +probono +prodemocratic +proffered +profilers +propeptide +propped +propyl +prosauropod +prostrata +prostyle +proteinbinding +proteus +pruinosus +pseudopodia +psychedelicprogressive +psychosurgery +publicizes +pufferfishes +pulverulenta +pupating +puree +purist +purser +quatrefoil +quickwitted +quinoline +quintessence +rackmounted +radiotelegraph +rainfed +ramen +rasp +rationalists +rava +ravage +rawhide +reaffirmation +reaffirms +rearranges +reassess +recension +recertification +recombinase +recompense +reconnection +reconvene +redaction +redeemable +redtail +redwoods +reemerge +refilling +refiner +reflectometry +reflow +reformism +reframing +reimagines +relaxin +relictual +relictus +remapped +remounted +renaissancestyle +reorientation +repetitious +replications +repo +reptiliomorphs +rerunning +resentful +reserpine +resettling +resourcebased +resourcepoor +retinoid +retrofuturistic +retrogaming +revalidated +rheumatologist +rhombicuboctahedron +ringlike +rippling +riskbased +rivulets +roadbuilding +rockfall +rockjazz +roguelikes +rolebased +rots +rotted +roughcut +ruficauda +rufousbellied +rufulus +rugicollis +rugosum +ryanodine +sagittata +sandbags +sansserifs +sapphires +sarcolemma +sarsaparilla +sawnwork +scalding +scalped +scatters +schoolwork +scitula +scrapbooks +screener +scuttling +seashell +secondperson +secundum +sedate +sedatives +seedeaters +seismograph +selfpaced +selfprotection +semestered +semitrailers +senates +serpentinite +sevencylinder +sevenvolume +sexrelated +sextuplets +sexualized +shallowdraft +shaven +shedroof +shootoff +showband +sibiricus +silicic +silicide +siliconbased +simplifications +singleseaters +singlestage +sinner +sixdisk +sixpointed +sixthmost +sixtyeighth +sixtythird +skewered +skillet +sl +slatecolored +sleepwake +slicer +smack +smallbusiness +smallformat +smeltwhiting +snakeweed +snobbery +sobriquets +sociopath +solanacearum +solida +songbooks +soror +soundman +southwestwards +spaceborne +spacey +spamdexing +spandrel +sparkplug +spats +spatulate +speakership +spearing +spedup +spillways +spinifer +splenomegaly +splints +spotlighted +spouts +spreaders +sprinkle +squalene +squeaking +ssh +standardbearer +startfinish +statedesignated +stereochemical +stereoscopy +steroidogenesis +stilllifes +storedvalue +stormtroopers +strapping +stressinduced +stretchy +strigatus +strigosus +stubs +stung +sua +subbranches +subcomponents +subdialects +submerge +submergence +submillimeter +subnets +subpart +subperiod +subsaharan +subscales +substellar +substructures +subtests +subtriangular +succinic +sulci +sunscreens +supercouples +superheroic +superioris +supplicant +suppository +suprarenal +supremo +surcharges +surinamensis +surreptitious +swaths +sweetgum +swingarm +swingman +sympathizing +tabloidstyle +tabulating +talkative +tamil +tampons +teapots +teasel +technologyoriented +tegu +telepathically +televisionradio +tenantsinchief +tendollar +tentrack +terpenoid +terroir +testtakers +tetrafluoroborate +tetrahydrobiopterin +tetrameric +tetrapyrrole +thenSenator +thiazide +thintoed +thirdcentury +threebedroom +threebladed +threespan +threetower +thriceweekly +thrifty +thrombi +thrombocytopenic +thst +thwaitesii +timeshifted +timestamps +tinkering +tomographic +topos +tourismrelated +traitorous +tramline +transect +transliterations +transracial +traviata +treatmentresistant +tribunus +trickortreating +tricolored +trifida +triloba +triphenylethylene +triphosphates +tripods +triremes +triterpenoid +trivially +troglobite +troodontids +trophoblastic +truncating +twang +twayblade +twentyminute +twinkling +twobladed +twochannel +twolined +typicus +ulFitr +umami +umlaut +unabashed +unassailable +unbanked +unbilled +uncertified +undeliverable +underscoring +undoubted +unfluted +unicornis +unipolar +unpolluted +unpunished +unquestioned +unreactive +untidy +untried +unwind +upanddown +upgradeable +utilis +vadose +valgus +vandalizing +vaulters +velcro +velocimetry +ventilators +ventromedial +vernaculars +vesical +vibrancy +vibrated +videoclips +videographers +vindictive +vinyls +virginalis +virginiae +viridula +vitis +vixen +vocalistkeyboardist +volitional +volunteerdriven +vomer +voyageurs +voyeuristic +vshaped +wXw +walteri +warmers +warps +washedup +washout +watchdogs +waterpower +watertube +wavesynthpop +weatherresistant +weathers +wellfounded +wellintentioned +wellkept +wheelbarrow +wheeling +whirling +whirlpools +whopping +whydah +wigeon +wilsonii +wipers +wiretaps +wombat +wooddecay +woofer +worksheet +worshiper +xerophytic +xi +xiangqi +yellowrumped +zebrina +zenkeri +zeylanicum +zig +zigzagging +zodiacal +zucchini +zum +abrogation +absolved +abstinenceonly +acclimatization +accrual +acousticelectric +acrimoniously +acutifolia +adagio +adduction +adjusters +adventive +advisement +african +aftershow +aftertax +afterthought +agassizii +airbrushed +alArab +alHakim +alHassan +albata +albidus +albiflora +albipuncta +albomarginata +alboplagiata +albosignata +albuginea +albumlength +aldolase +allinstrumental +allodial +allyoucaneat +alphaketoglutaratedependent +altaica +alteregos +alternatingly +alternatus +ama +amara +amazement +amazingly +amirs +anachronistically +anaconda +anchorite +ancilla +angiotensinconverting +ankylosaurid +anodes +anoles +ans +anthozoans +antiApartheid +antiIsrael +antigenbinding +antigonadotropic +antiinfective +antiporter +apheresis +apologizes +apotheosis +appetites +applicationlevel +applicators +appraisers +arbitrations +arboriculture +archerfish +archeri +arcus +argumentum +armeniaca +arrester +arroyos +artillerymen +ascendens +assertiveness +assize +aterrimus +atmospherics +atricornis +atripennis +attentiondeficit +august +auricle +automorphic +autotransportable +avec +aviso +awardgiving +backbenches +backboard +backwardness +bakelite +ballclub +balteatus +bancrofti +bangles +banishing +baritones +baronetage +barreled +basking +basque +batteryelectric +batterys +battlegroup +beagles +behead +belowaverage +belting +benga +benthicola +betaadrenergic +beth +bicinctus +bicolorata +bigtime +bilberry +bilinear +bioactivity +biobank +biodefense +bioengineered +biographic +biotope +biplagiata +birdcage +birdsofparadise +biroi +birthplaces +birthwort +bisphenol +bistatic +biz +blacklists +blacknecked +blackwood +blanco +blemishes +bloke +bludgeoned +bluebanded +bluescreen +blushing +bodhran +bodyboarder +bole +bollards +bonang +bonanza +bonnets +bonspiels +bottomlands +bourse +bower +boxcar +brachialis +breakdance +breakpoints +brigand +brigsloops +broadbanded +broadbilled +bronchoconstriction +bronchodilator +broomlike +brownbacked +brutish +bryanti +bullfight +burgher +butlers +butternut +buyback +cEvin +calendrical +callups +calluses +camerawork +candidum +canopied +capitalintensive +cara +carburetted +cardstock +carolinus +carryout +cartouches +carvalhoi +casta +catsuit +causeandeffect +celeste +cellmate +centerhall +ceramidase +chaindriven +chalcedony +chargeback +charterer +chelicerate +chemistries +chessplaying +chiller +chines +chiseled +cholestasis +chowk +churchrelated +circinata +circumnavigating +cityoperated +classy +clawless +cleansed +clicker +climaxes +closeby +cmdexe +coFounder +coactivators +coarctata +coattails +cobranding +cockatoos +cocreate +coediting +coeditorsinchief +cognitivebehavioral +cohors +collarti +collateralized +comatus +combusted +communityfocused +competently +complacent +composerlyricist +composita +compressus +compta +concentrators +conceptualizations +concordant +concretely +congenial +connectionist +conquers +conservatoire +conservatoires +conservatorship +constitutionalism +consumerist +contactor +contiguity +contortion +contrastingly +contravened +cooccur +coopers +coots +copulatory +coqui +cordgrass +cordoned +cornstarch +corpsman +cos +cosponsoring +costsharing +cowinners +crabbing +crackdowns +cranked +cranking +cratering +cremations +crewserved +crimp +cristal +croaking +crocata +cronies +crosshead +crossreference +crosssector +crosswalks +crosswind +cruck +cryptids +cs +ctenophores +cubism +cuboctahedron +culdesacs +cunt +cupbearer +cupreus +curculio +curlytailed +curtailment +curtisii +cusk +customisation +cyberterrorism +cystatin +dAsti +dEUS +dacoits +dais +dancedrama +datacenters +davisi +deacetylation +decedent +decompressing +decoratus +deers +deferment +deforms +degreelevel +deliquescent +delos +demystify +denouement +dentifera +deodorants +deoxyribonucleic +depersonalization +deposittaking +depress +dermoid +desertorum +desertparsley +designationKorea +despairing +despise +deters +dhol +dialers +diapsids +diffractive +digoxin +dildo +directbroadcast +directedenergy +disaffiliation +disagreeing +disburse +discalis +discoidalis +discouragement +discoverable +dismasted +disorienting +disparage +disqualifying +distillates +distracts +distributaries +distrusted +diterpenoid +djent +documentoriented +dogstail +dole +domainbinding +dopant +doris +dotterel +doused +downplay +doxorubicin +dreamt +dropseed +dropwort +drosophila +drownings +drugresistant +duallicensed +duals +duckweed +dud +duff +dukun +dullcolored +duplicity +dyad +dzong +eccentrics +ecclesiastica +echinatus +echinoids +econometrician +edule +eggshells +eicosapentaenoic +eightpointed +eightynine +elatus +elderberry +eloped +emarginatus +emboli +emceed +emotionless +endogenously +endproducts +enganensis +enlightening +enquire +ensuite +enterpriseclass +enumerative +envied +eos +epicyclic +epidermolysis +epitaphs +epoxidation +equalizers +equids +equivocal +ergo +ergonomically +ergosphere +errorcorrecting +errorfree +erythropus +etcetera +ethnocentrism +etiologies +eusociality +evaporites +exacerbating +exarchate +exes +exome +exostoses +expansionary +expletive +exservice +exsoldier +extractors +exurb +exurban +eyeglass +eyesore +fa +factitious +factorybuilt +falcatus +fallaciosa +falter +famiglia +fanfiction +fasciculi +fasti +fatherland +faulttolerance +featherlike +feigning +felderi +felids +fended +ferch +fiend +fifthbest +filles +finalization +finetune +finlayi +firebrand +fireships +firstcentury +firsthalf +firstlook +fissionable +fixative +fixedgear +fixer +flagellation +flamines +flattery +flattish +flavitarsis +fleurdelis +flexi +floodlighting +floorboards +floriculture +flowery +flowthrough +flumes +folkloristics +folky +fooling +forebay +forestfly +forfeits +forgettable +formant +formicid +formyl +fornication +fortlets +fossilfuel +fossilize +fourinhand +fourletter +fourspotted +fourtholdest +foxhound +framebased +framebyframe +fraterculus +freeflow +freeskier +freespace +freezethaw +freshener +fritillaries +fronton +fruitfly +fucose +fullterm +fume +functionals +funksoul +funneling +furling +furtive +fuscicornis +galago +gallate +gamebird +gameboard +gamelike +gaon +garnets +gastropub +gaur +gearequipped +geeky +gemellus +genrebending +gentlemanly +germinates +germplasm +giudice +glassenclosed +glassreinforced +glens +globalizing +gloria +glycolytic +gnarled +goalies +gobetween +goldcertified +gondii +gorgonopsian +gouged +gourmand +grackle +graham +granatum +grandifolia +gravitationalwave +graybacked +greataunt +greenishcream +greenlight +groupie +groupthink +gulfs +guttulata +gyrase +haberdasher +hackerspace +hackle +hackney +hairtail +halfmillion +halfpenny +halfsiblings +hallandparlor +hallparlor +hamatus +handouts +handrail +haphazardly +harbourside +hardwearing +hart +hatchbacks +hauler +havo +hazmat +headfirst +headscarf +headup +heidelbergensis +helleri +hemosiderin +henna +hermaphroditism +hermitages +heterologous +heteromorph +higherresolution +highstreet +hillstream +hindmargin +histiocytosis +hitches +hockeys +hoffmanni +hollyhock +homebuilders +homefront +homeomorphic +homestyle +homodimeric +hoodies +hoodoo +hookeriana +hornero +hornpipe +hostelry +hotelcasino +houseplants +hulks +hunky +hydrologist +hydroxides +hydroxycinnamic +hyphy +hypodermis +iCal +iSeries +ich +idiots +idolized +illumos +imbedded +immunogen +impressionists +incepted +incessantly +incharacter +indentures +independentminded +indevelopment +indiealternative +indirection +indisputable +indusia +industrialize +industryled +industryrelated +inear +inexhaustible +infilling +inflates +infraspecific +infuse +inquires +insourcing +instinctual +insulae +insulindependent +interactionism +interbellum +interdental +interdepartmental +intermarriages +intermezzo +internetconnected +interparliamentary +interrelation +intersperses +interstates +intertrochanteric +invision +ironweed +irresponsibility +irrevocably +isolators +jammers +jetliner +jewelcase +jobber +jobbing +judicata +july +kPa +kabbalistic +kame +kantele +kashmirensis +katakana +kebeles +kecap +kendo +keypunch +keyring +kickstart +kidfriendly +kindergarteners +kirtan +kitschy +kmlong +kris +kuna +kungfu +lacordairei +lacunae +laevicollis +lamas +laminating +larder +largescaled +lascar +latenineteenth +latens +latipes +latticework +lawrelated +leatherbound +lefteye +legislating +legspinner +lessors +leucoptera +levitate +lexicographical +likens +linearifolia +linkers +lipodystrophy +liposuction +lipsynced +listenable +listenedto +literals +liturgically +lobotomy +locallyowned +lockable +lollipops +longed +longiceps +longulus +longum +loon +loopback +lotic +lowerranking +lowestranked +lowflying +lucasi +lumberjacks +luzonensis +lysozyme +madcap +madrassa +maegashira +mafiosi +magellanica +magnetospheric +majorityblack +maladies +mandocello +mandola +manhours +manioc +mansoni +manubrium +marae +marca +margaritifera +marginalia +marihuana +maritimum +marmorea +marxist +massless +maternally +matric +matroid +maura +meadowrue +mechanoreceptors +meditating +mediumdensity +mediumlift +megacephalus +megacorporation +megaphone +megaprojects +melaleuca +melanops +melidectes +mentalhealth +merchandisers +mercurial +merriment +mesentery +metacognitive +metalpoor +metaphoric +micaceous +microhabitats +microservices +microstrip +midafternoon +midinfrared +midnd +midnineteenthcentury +midribs +midvein +mimeograph +mirus +misprinted +missional +mistranslated +mixedmember +modillions +moerens +moistened +moldy +molerats +moluccensis +moneylaundering +mongrel +monofilament +monomorphic +monoterpene +monotonic +monte +montivaga +moonwort +moorhens +morel +morels +motorable +mottoes +mouses +mousse +mullioned +multibattalion +multicar +multienzyme +multipass +multiphoton +multitenant +murinus +musicaldrama +musicianproducer +mutagen +muting +mycorrhiza +mycosis +nakedtailed +nam +nannies +nanoelectronics +naphthoylindole +nasopharyngeal +natured +nawabs +nearvertical +negates +neighborly +neoclassicism +nepal +neptunium +neuromuscularblocking +neuropathies +neuropsychiatrist +neurovascular +neutralizes +neutrally +nigropunctatus +ninepart +nitrification +nitriles +nitrobenzene +nl +noball +noche +nofault +nonNATO +nonbenzodiazepine +nonconference +nondigital +nonfarm +nonhomologous +nonintrusive +nonlinguistic +nonperishable +nonprogrammers +nonsmokers +nonsocialist +nonstarters +noradrenergic +notational +notoungulate +nowlost +nowobsolete +nozze +nuchalis +nullifying +observatorys +ocarina +ocellate +ogre +ol +oleifera +omnipresent +onematch +oneness +oofamily +openheart +openwater +ophiolite +oppressing +oratorical +orientalism +orofacial +oropharynx +oseltamivir +ossicones +osteitis +ostium +outfitters +outgunned +outlast +ovalifolia +overextended +overflights +overlaeti +overwork +ovis +oxygenate +pachyphylla +packhouse +paleobotany +panpipes +papillomas +parallelus +parasitizing +parenchymal +parenthesis +parvidens +parvocellular +passionflower +pastoring +pathfinding +patricius +pax +paymaster +pcode +peacemaker +peatlands +peddlers +peduncularis +pendent +pendulums +penicillatus +penmanship +pennywort +pentacle +pepperweed +peripheries +peristyle +permanganate +peronii +peroxisomes +personam +personifies +personify +petaflops +petascale +pharmacogenomics +phenylpropanoid +phenytoin +phonologically +phototropism +phthalic +pickpocketing +pilsbryi +pilsner +pinetorum +pinks +pinnule +pinwheel +piranha +plainer +planifrons +platelike +pleases +pneumatically +pneumococcal +poinsettia +polaris +policemans +polyene +polyglutamine +polymerize +polymorph +pompano +pontica +pontis +popindie +populating +porcupinefish +porgy +porins +postIndependence +postcentral +postconceptual +postdoctorate +potently +pothole +potluck +pratti +preadolescent +preceptor +precut +predesignated +predisposes +predraft +preempting +preestablished +prefixing +prepartition +preplanned +presacral +presupposition +priapism +primatology +primitively +probationers +proclivity +producersongwriter +prolate +pronouncement +propofol +proprotein +provably +provocatively +psaltery +pseudodocumentary +psycho +psychopaths +pupillage +purer +purplishred +pushtotalk +pushup +putrefaction +pyrethroid +pyrimidines +quadrupeds +quango +quash +quatrain +quatrains +quelling +quietest +radicalisation +raindrops +ramosissima +rapae +ratite +reanimation +reapplied +reassured +recede +recensions +reclassed +reconnecting +recordequalling +recurva +recurvata +redbelly +redecoration +redhead +redtipped +reedgrass +reefing +refiners +refoundation +refusals +regality +registrant +reinsurer +reinterprets +rejuvenating +relished +remailer +remotest +renegotiation +repented +repopulated +reptilelike +requisites +rerum +resents +reshuffling +retraced +revere +reversibility +revises +ribcage +ribonucleic +richardsoni +richteri +riggers +rightangled +rightsbased +ringens +romani +rosei +rotoscoping +ruggedized +runins +rushhour +sabermetrics +sabino +sachets +safeworking +safflower +saligna +saluting +salvo +sambo +sandbanks +sanddune +santa +santoor +sapling +sapo +scammers +scaphopod +scaring +schlegelii +scopa +scrapbook +scriptable +scuffle +seabrai +seagulls +seamer +searcher +seaworthiness +secondfastest +seizes +selectman +selenocysteine +selfbuilt +selfdescribing +selfstudy +seltzer +semesterlong +semilunaris +semispinalis +senatus +senex +sequins +serjeant +serogroup +serpentina +serviceability +serviceberry +setigera +sevenstring +seventyfifth +sextant +shackle +shapeshift +sharppointed +shia +shifters +shortfin +shorting +shredder +shrimplike +shuttled +sidelong +sidewalls +sieboldii +sifted +signaller +signees +signlanguage +silane +silvanid +silverline +simpleton +sinense +singersong +singlespeed +siphonophores +sixgame +sixpage +skapop +skewers +skinning +slaveholding +slavemaking +sliceoflife +slideout +sloe +slung +snRNP +sniping +snowi +snowstorms +solitons +solstices +somalica +sparkignition +speakerphone +specialedition +specialinterest +speedrunning +spicatum +spinet +spinicollis +spiteful +splines +spongelike +sportstalk +spotty +sprain +sprig +squamosal +squeaker +stacker +stairwells +stammer +stealthbased +steed +steeplypitched +stemcell +stepchild +stephensi +stepper +stereographic +stifled +stillactive +stipes +stoic +stoker +stolon +stoop +storeroom +strategical +strategize +stroked +strollers +strum +strutting +stuarti +studenttofaculty +stuntmen +subassemblies +subcarriers +subcomponent +subdermal +subframe +subfund +subline +submariner +submersion +submucosa +submunicipality +subnormal +subscapularis +subsectors +subtended +subverts +subvillage +sues +suffusion +sunbirds +supercooled +superheroines +superlightweight +superstores +superstructures +suspiciously +swamphen +swash +sweatshirts +sweaty +sylvaticus +symbolical +synanthropic +syndactyly +syntrophic +syslog +tablecloths +tabletennis +takeup +talker +talkshows +tamarack +tapeless +taunts +technopop +teledrama +telencephalon +teleprompter +teleserial +telethons +temperaturecontrolled +tenderloin +tergum +terminata +terminators +terpenoids +terricola +testisspecific +tetratricopeptide +thawed +theists +thenmayor +theraphosid +thethen +thieving +thioester +threebarbeled +threemovement +threesome +threewheelers +thtier +thuringiensis +tightens +tightest +tightfitting +tillers +timberframe +timetrial +titlewinning +toolmaking +torpor +tors +torta +touchscreenbased +touronly +toxicities +trabeculae +traceroute +tractortrailer +tradeshow +tragacanth +transesterification +transposing +trawls +treadle +trendsetting +triaxle +tricksters +triennially +trifolii +trilateral +trimarans +trimethylamine +trippy +trumpetshaped +trunking +tsetse +tuneful +turnstile +twelvebar +twosport +typographers +udder +uilleann +ulmi +ultrashort +umbilicata +unabashedly +unaffordable +unblack +unbridled +unburned +unceremoniously +unconvincing +uncountable +uncrewed +undercooked +undercroft +undeserved +unfocused +unicode +uninvited +unities +unknowable +unleavened +unmask +unsatisfying +unselected +unselfish +unshared +unthinkable +updateThe +upgradation +upturn +uremia +uremic +vaccinepreventable +vacuolar +vaga +valedictory +valency +valvetrain +vanillin +variegation +vascularized +vasomotor +vedette +vedic +veld +venae +venata +venters +vergence +verifications +versant +vertextransitive +vexillum +viewport +viking +violoncello +viridipennis +virility +vise +vitriol +viva +volans +volcanically +voussoirs +voyaging +vulcanization +walkthroughs +walruses +wanderers +warders +wardrama +warplanes +watchOS +wattleeye +weaponized +wearables +webtoon +wedging +welldressed +wellmarked +wellplanned +wellused +welwitschii +westernized +wetness +wetsuit +whereafter +whims +whitecollared +whiteface +whiteflies +whiteowned +whitepainted +wholegenome +wholeheartedly +widemouthed +wireguided +wishful +wolflike +woodruff +woodturning +workup +wreaks +writeup +xB +yachtsmen +yearbyyear +yonipitha +yoyos +zigzags +zonatus +zookeeper +abolishes +absconded +academical +acceptances +accosted +achilid +acidfast +acrimony +actionplatform +acutangula +addins +admonition +advantaged +adversarys +advertisingsupported +ae +aerostat +aestivalis +affirmatively +aflatoxin +aftertouch +agnostid +agri +agronomic +airfoils +airguns +alKarim +albicosta +albomaculatus +allover +allylic +almighty +ambercolored +ambushing +amelioration +amenorrhea +americium +aminotransferase +amity +amperes +anagrams +anamorphs +anesthetized +anglicization +angulifera +anonymised +antagonized +antbirds +antechamber +antennatus +anthracycline +anthropomorphism +antiCatholicism +anticensorship +antifungals +antigenantibody +antithetical +anxiogenic +api +apoplexy +appendectomy +apportioning +aramid +araucaria +arbitrate +arbitrated +arboreus +arcana +areolata +aroundtheclock +arsenite +arterials +arteriole +arteritis +ascidians +ascomycetous +asemic +atrovirens +attestations +aucuparia +auroras +australasiae +autobiographic +autosomes +autotrophs +avenger +avocation +awakes +axially +azureus +backarc +backpropagation +backstretch +backwardscompatible +bagpiper +bailiwick +bairdi +ballshaped +bandcamp +bandurist +bap +barbecuing +barbeled +barite +basketballs +battens +bazar +beaklike +beaming +bearish +beatnik +becard +bedchamber +beddomei +beefsteak +beeman +bellbird +benchtop +benefaction +bengal +benzoin +betacatenin +betalactamase +biconvex +bioassay +bioethical +biomaterial +biomolecule +bioorganic +bipropellant +birchbark +birder +bishopelect +bitrates +bizarrely +blackcap +blooper +bluesmen +bluetongued +bollworm +bolton +bombmaking +bombsight +bookends +bookmarklet +bouvieri +bowtie +brasiliana +brazen +breastfed +breastfeed +breastwork +breedspecific +brent +briefcases +brokendown +brushy +budgerigar +bulkier +bullatus +bullous +burqa +bushbaby +bushman +busk +busted +butterscotch +buttes +cabriolet +caddis +cadenza +caimans +caliper +calorific +calorimeter +calorimetry +calpain +camerunica +canaliculi +canards +candphilol +canoekayak +cantaloupe +cantankerous +canted +capitatus +carbapenem +carborundum +cardinality +carelessly +cargopassenger +carica +carmakers +carnivory +casebooks +casework +castello +casters +castinplace +catechin +cathodic +cavalcade +cavalier +ce +centralisation +centralizes +centurions +chalked +chalybea +chasmogamous +chicane +chiefofstaff +chilies +chloroquine +chondrodysplasia +churchstate +chylomicrons +cinchweed +cineraria +circulars +citri +citrifolia +cittern +citybased +clavatus +cleat +cleaver +climaxed +clingfish +clomifene +closein +clothesline +cloverlike +coalburning +coasting +cochineal +cocitation +codependency +coffeetable +coinages +coir +coitus +collimator +colorblind +colostrum +colourings +comefrombehind +comfrey +commas +commensals +commissar +comorbidities +comosa +compactly +compactor +compartmentalized +condensers +condyles +conferral +congratulate +congratulating +congratulatory +congregationalist +conjunctival +conjured +consciousnessraising +consecrator +constructional +consulships +consumerfacing +consumerlevel +contentcontrol +contextualized +continuouslyoperating +contrabassoon +contravenes +contrition +convertases +coolness +copiously +coppers +copresidents +coronae +corrector +correlative +corrosionresistant +corrupts +corvina +coshowrunner +cosmetically +councilowned +counterparties +countersued +cowbells +cp +crankshafts +crawfish +creamwhite +credo +creepers +crematory +crocheting +crosscommunity +crossfertilization +crossselling +crouching +crowfoot +crucifers +crushers +cryptanalytic +cucullatus +cuirass +cuneatus +cupule +curios +cuticular +cyclopentadiene +cypresses +cystadenoma +dArt +dEtat +dalli +datestone +dawah +dawning +dayschool +deacetylases +deactivating +deadmaus +debilitated +debugged +decently +decidable +decimation +deeplying +deflector +defusing +degenerating +degranulation +dehumanization +delimiting +denitrification +deoxyribonuclease +deoxyribonucleotides +depresses +depressum +depute +deputizing +derivate +derivational +dermatologic +desertions +desolation +despotic +destitution +detonates +devolving +dhonneur +diacritic +diene +diffusely +diffusive +dilating +diluta +dilutions +dimorph +diphallia +dipoles +disallows +disassembling +discontinuance +discors +discrepans +dishware +disinterested +dispositional +dissuaded +distans +distinctness +distrustful +disunity +dit +divisor +docklands +docusoap +doghouse +dossiers +doubleact +doubledecked +doubleentry +doublehanded +downsides +dragonfish +dragonfishes +dragonlike +drapers +drinkware +drowns +dryas +duc +dugongs +dunks +dustjacket +dyslipidemia +earlyseason +easterncentral +educationrelated +eduskunta +effectual +effusive +eicosanoids +eigenvalue +eighteenyear +eighthmost +eighthplace +einer +elSheik +elastica +electrohydraulic +electronicore +elephantiasis +ellipses +ellipticus +eloquently +embarrassingly +embellishing +emulations +enantioselective +endodermis +endoparasitoids +engrossed +enlargements +enliven +enlivened +enrollees +enterovirus +entheogen +entrained +entrees +enumerating +envelops +eosin +erasable +eremita +erosa +erythraea +estrus +ethicists +ethnical +ethnographical +etv +eu +eugeniae +euglossine +european +euskelian +everetti +evicting +exSoviet +excising +exerciseinduced +expressible +exslaves +exstudents +extemporaneous +externa +externality +extrachromosomal +extremophiles +facebook +facile +faired +fairings +fancreated +fantasyscience +fasciatum +fasciolata +fasttracked +fathering +favelas +fc +feeforservice +femaleled +femoratus +fermentans +fey +fiefdoms +fifteens +figureheads +filebased +filers +filmTV +filmi +filmrelated +fils +financings +finned +firehouses +fireproofing +firstofitskind +firstwave +fisheri +fivecard +fivespeed +flagstaff +flamethrowers +flatmate +flaxseed +flirted +floodprone +floorcrossing +floored +fluorouracil +foeticide +folkoriented +followedup +foodies +footers +foraged +fording +foreignexchange +foresees +forestland +forgings +fornix +fortifying +fragariae +framerate +freckles +freeflying +fricatives +friendlier +frogmen +frothy +fructosebisphosphate +fullline +funnier +furosemide +futurists +gTLDs +gadolinium +galeata +gamekeeper +gamemasters +gassed +gauge +gcc +geminatus +genderless +gendhing +generelated +genial +gentium +geoengineering +geoffroyi +geosynthetics +gettogethers +geup +gharanas +gibberish +giftgiving +gimp +giorni +giudicato +glaber +glaberrima +glassblowing +glasshouse +glasslike +glassworks +gliadin +globetrotting +glucosamine +goaround +goldenbellied +goldenbush +goldeneye +goldenweed +goldfield +golem +gonophores +gorget +gossypii +goths +grabbers +gradations +grammaticalization +graptolite +gratifying +graythroated +greeneyed +gregaria +gridlike +griffins +grin +grottos +grownups +guadalupensis +guar +guernseys +gullible +gusty +gynecomastia +hAlba +hacktivism +halfyear +handblown +handwashing +happiest +haptophytes +haptor +harmlessly +harry +hast +hateful +headbands +headhouse +headhunter +headstander +heave +heavymetal +heightening +helicinids +helixturnhelix +helplines +hemagglutinin +hemostatic +heterodont +heterophyllus +heterosexism +hexameter +hexane +hexose +hiemalis +hierarchs +highcaliber +highestplaced +hightraffic +hiked +hilli +hirtus +hispanicized +hoarse +hognosed +holdouts +holism +hollowedout +holoprosencephaly +homemakers +homesick +homestays +homophile +hoplite +hornblende +hors +hostbased +hostguest +hostname +http +huddle +humbuckers +humpbacked +humpless +hydantoin +hydrogels +hydrometer +hygienist +hyperparasitoids +hyperstimulation +hyperthermophilic +hypnagogic +hypoglycemic +hyrax +iBook +iHeartMedias +ichthyology +ignobilis +iguanodontian +illegitimacy +illustris +imberbis +immobilizing +immunizations +immunoassays +impasto +impermissible +implementable +impregnation +impressa +impressment +incites +indemnify +indignant +inducting +industrials +infaunal +infiltrators +infirmity +inflicts +informality +informations +inhalers +inheritor +initialize +injunctive +innersouthern +innerwestern +inseason +insides +insincere +interbred +intercalation +intercede +intercommunal +interconnectivity +interferons +intermarry +interna +internodes +interrogates +interrogative +intervarsity +interweave +intracytoplasmic +intrathecal +intraventricular +inulin +inuse +invariable +invective +inventoried +inviteonly +invulnerable +ip +ironman +ironmasters +irrepressible +isabellae +ischaemia +isochronous +isomerism +jamesonii +jammer +jazzbased +jird +joist +jubilant +jumbled +juristic +kaffir +kaiju +kalam +karri +kebabs +keratins +khagan +kickstarter +kiloton +kilts +kinder +kinetochores +kirbyi +kneading +kneelength +knell +knits +knobtailed +lacertid +lactam +ladybirds +lagomorphs +lancehead +landlordtenant +laparotomy +lastsecond +lateonset +launder +lawnmower +leges +lemmonii +lemonscented +lenticularis +leprechaun +leucophrys +leucopterus +leucosticta +leucotis +likenamed +lilting +limo +lineblue +lineside +lipoic +liquidus +lithified +livres +ll +loancharter +loansharking +lobar +lockedin +lodgement +logspace +lomatium +longarm +longcourse +longicaudatus +longimanus +longissimus +longtrack +longwinged +lowcalorie +lowrider +loxensis +loya +lp +lucens +luciferase +lunules +lusitanica +luteal +luteolus +luxuriant +lycophyte +lyse +lysophosphatidic +machineguns +macrocycle +macrurus +magnetohydrodynamics +maidservant +maingayi +majori +majoritarian +majoritys +majoritywhite +malar +maleate +malic +malocclusion +malpractices +mama +mamma +mammalogist +manageability +mange +mangle +manofwar +manyflowered +maraschino +marathoners +marcher +marginatum +marketleading +marls +martens +martensi +masseter +masseur +masterminding +matador +maypole +mediterranean +megabits +megacephala +mei +melancholia +melange +melitensis +memorializing +mending +menhir +merciful +merong +merostomatan +metalsmith +metasomatism +miRa +microcrystalline +microdistillery +microform +microhouse +microloans +micronucleus +microstructures +microvascular +middleblocker +middlegame +middlesized +midtour +militarythemed +milkfish +milkweeds +millrace +mimed +mineralocorticoid +minutissimus +mirabile +misbehaving +misread +missense +missteps +mitchelli +mitogen +mitosporic +mitten +mixologist +mixtus +modernists +modica +modillioned +modularization +moisturizer +monacha +monetizing +monocotyledon +monogenetic +monolayers +monopodial +monotype +montivagus +moralizing +mordax +mostnominated +mot +muay +mudslide +multibeam +multichip +multidisk +multiepisode +multifrequency +multigames +multispecies +multivenue +multiwavelength +murrayi +musichall +musicmaking +muskrats +mustaches +mutism +myopic +myxobacteria +nForce +nSpace +nailing +nanocrystals +nanomolar +nanosatellites +narcissus +nasogastric +nattereri +naturale +nay +ndth +ndtier +neckties +necromancer +nectarine +needlefish +neocaledonica +neoconservatives +neonatology +nephrons +netballer +neurocognitive +neuroectodermal +neurolinguistic +neuropharmacology +neutering +nevercompleted +nexin +nextgen +nicht +nigerrimus +nigritarsis +ninthyear +nitrogencontaining +nitrox +nodosaurid +nokill +nonATPase +nonAustralian +nonRussian +nonTest +nonathletic +nonbusiness +noncash +noncooperation +nonfat +nonlife +nonmarket +nonparallel +nonprofitmaking +nonprogressive +nonrecognition +nonsecure +nonspecialists +nontest +nonthreatening +nontrinitarian +nonurban +nonvisual +northnortheastern +nosewheel +notarized +nowdiscontinued +nth +nuclearencoded +nucleons +nullius +nutraceuticals +nympha +oakleaf +obfuscated +obligor +observables +oceanside +ochroleuca +octal +odontocete +offandon +offprice +offseasons +ohm +ohne +oligochaetes +oligolectic +omniscience +oncogenesis +oncourt +onebyone +oneclick +onepercenter +onetrack +onevote +ontheground +opacities +openloop +orality +orangery +orbiculata +orgy +orientis +origen +orthogonally +orthotics +osteotomy +outliner +outofwork +overarm +overexploited +overloads +overprint +overrode +oversea +overshooting +packetbased +painkiller +palatini +palawanensis +paleoclimatology +palinopsia +pallor +palmoplantar +panacea +pandanus +panegyric +panfish +pantherina +papillomatosis +paraAlpine +paralogous +paranasal +parapatric +parapodia +paraprofessional +paratriathlon +parceled +parkinsonism +parlay +parotia +passengeronly +passphrase +pasty +pathognomonic +pavers +pectinate +pedagogic +peephole +pensive +pensylvanica +pentito +perakensis +periapical +peristaltic +persecuting +pesantren +pg +pharma +pharmacodynamic +phaser +phenanthrene +phencyclidine +phenethylamines +phenolics +phenylpiperazine +phoebe +phosphatic +phosphoglycerate +phrynosomatid +phyllodes +phyllosilicate +phyllosphere +physic +physicianassisted +pianistcomposer +pictographs +picturing +piercer +pigmentosum +pilaster +pileatus +pillaging +pinkishpurple +piratethemed +piscivorous +pistachios +pitchman +pithy +placode +planer +plasmons +plasterboard +platensis +platyphylla +ploidy +plumaged +plumbeous +plummeting +plutino +podiatric +podiatrist +polishes +polyamorous +polyatomic +polycythemia +polygenic +polymorphs +polyneuropathy +polyol +pondering +poojas +popmusic +poppier +pornographer +porphyrins +porteri +postAmerican +potentiality +potentiate +potentiometers +prasad +prayerbook +preamplifiers +precariously +preclearance +precollegiate +prednisone +prek +prenominal +prepainted +preparers +prepublication +preregistration +presale +primality +priscus +privatizing +proConfederate +proGaddafi +proa +probationer +procedurals +prodigal +producerDJ +programmability +progrock +prominences +prominens +promotor +proms +pronominal +prospected +proteobacterium +provirus +prowar +proximally +pruritic +psammophila +pseudoD +pseudouridine +psychoses +pterostigma +publishable +puddling +pugnax +pulcherrimus +pulsejet +pumpedstorage +punchy +puppeteered +purifiers +purplepink +purples +pv +pylorus +pyram +qigong +quadricolor +quadrifasciata +quadriguttata +quadruped +quadrupole +quantifiers +quashing +quasicrystals +quasilegislative +quasiparticles +quasisatellite +quasispecies +quebracho +questionably +quiescence +radiobiology +radiographer +radiographers +radishes +railfan +rajasthan +ramblers +rapamycin +rapture +ras +ratites +rauisuchid +raving +realignments +realitydocumentary +reapers +rearmost +reauthorize +rebeli +receptormediated +recombine +recombining +reconciles +reconnaissancebomber +recordholding +reddening +reddishgreen +redistributing +redrew +redrumped +redshirting +reductases +reedbed +reformminded +refried +reggaedancehall +reggaeinfluenced +reimplemented +rekindling +renderers +renovates +reoccupation +reopens +repels +repetitiveness +replicative +reprieved +reptiliomorph +rereading +reroofed +residuals +respectably +resuscitated +retracing +retrievable +retroflex +retroperitoneal +rhomboidea +ribald +riddims +rifleman +rightangle +rimonabant +ringback +roadrail +roams +robertsi +robs +rockcountry +rockpsychedelic +rollerblading +rondo +rookielevel +rootkits +rootsrock +rotundatus +roughscaled +rufouswinged +rulebooks +rune +rustcolored +rut +saboteur +saccharin +saddlebags +safetyrelated +salvini +samiti +sampy +sander +sanders +sandmat +sandwiching +sangha +sannyasa +sapper +satraps +sawmilling +sawscaled +sc +schoollevel +schoolmate +scoped +scow +scruffy +scrummaging +scrupulous +scrutinizes +scrutinizing +scute +seascape +seatbelts +secondever +secondseeded +secotioid +sedanbased +segregates +selfdesigned +selfdeveloped +selfdisclosure +selfexamination +selfloathing +selfparody +selfregulated +selfrighting +selo +semirufa +semisequel +sen +seneschal +sensationally +serializing +sericans +serosa +serra +sessilis +sevengill +seventeenvolume +seventerm +sexratio +shadowdamsel +shallowness +sharpnose +shearer +shewolf +shikimate +shined +shins +shipoftheline +shortbilled +shortbread +shorteared +shortnosed +shortseason +shoulderlaunched +showgirls +shrikethrush +shuffles +shul +shun +sickened +sideboard +siderophore +siglum +signifer +silentera +silicones +silvicultural +simpleminded +singermusician +singersongwriterproducer +singleCD +singleminded +singleparent +sitio +situs +sixbay +sixcar +sixthround +sixthseason +sixtyfourth +skerry +skintight +skyrocketing +slaw +slott +slowwave +smothering +snaketail +snowdrop +snowplow +snug +sociability +sociolect +socotranus +solenodon +soloalbum +somnolence +soulinfluenced +souring +soursop +spacesuits +spacethemed +spaniels +spatter +spatulashaped +specifier +speckling +speedily +sphenopalatine +spicier +spindlework +spinipes +spintronics +spools +sporeproducing +sportscasting +sporulation +squadbased +squamates +squeak +stablemates +stackable +steelpan +steelworkers +stejnegeri +stench +stenography +steyermarkii +stippling +stockman +stonefish +storybased +stramineus +strath +streamflow +striatula +strictum +subarea +subcommunities +subdean +subfasciatus +sublist +sublittoral +subname +subneighborhood +subprovincial +subranges +subsume +subterranea +suburbicarian +succubus +sulfinic +sulfonyl +sulphureus +sunbeam +sunsynchronous +supercharging +superimpose +superposed +supposing +surahs +surfed +switchbacks +swordshaped +symmetrickey +symptomatology +syndromic +syngas +synthwave +syriaca +syrinx +tabacum +tandemseat +teacherstudent +technetiumm +technologyfocused +tectonically +temperaturedependent +tempering +tendril +tenebrosus +tenmember +tensioner +tenthplace +tenuissima +teraflops +terpene +thankful +theist +thenexisting +thenincumbent +theophylline +thermophilus +thickens +thionyl +thirdbestselling +thirdstring +threebanded +threecornered +threeengine +threepronged +throes +throughhole +tics +timberland +timeframes +timezone +titanosaurian +titers +tolllike +toms +tong +topaz +topgallant +topgrade +topside +torques +tourmaline +towerlike +townsendii +toxicant +toxoid +transPacific +transcriptomics +transferases +translucida +treadmills +treatys +treecovered +treedwelling +treetops +triazine +trickling +tricounty +trifasciatus +trigone +trinucleotide +triplefins +trisphosphate +triumvirs +trochlea +trombonists +trucked +trumped +truncatipennis +tumorassociated +tunebook +tung +turbojets +turcica +turgor +turnarounds +twodivision +twoinch +typespecies +ugandae +ugliness +uliginosa +ulnaris +umbrina +unadulterated +unallocated +uncirculated +unconstrained +underfloor +underhand +underrecognized +underresourced +understaffed +undulations +unenclosed +unfertilized +unfused +unifasciatus +uninstalled +unleaded +unmade +unmapped +unpredictably +unraveled +unreinforced +unstressed +untraceable +unviable +unwise +uralensis +urologists +ussuriensis +vagaries +validators +valorem +vancouverensis +varicella +venules +verum +victualling +vilification +violaceous +violascens +vittatum +vittipennis +vocalese +vocalsbass +voicings +vrh +vulpina +vv +warranting +wasis +watercolourist +waterproofed +wavefunction +webcap +wedded +wellarmed +wellbehaved +wellloved +wellremembered +wetsuits +whitestriped +wholemeal +whore +wicketkeeping +widereaching +wielder +wilful +windfarm +woodchips +woodfired +wordlist +workrate +wormholes +woundup +xylose +yarrow +yawning +yuhina +zag +zombiethemed +zooms +zygomorphic +abaca +abrogate +abscisic +abseiling +absolve +absorptive +acacias +acceptably +achondrites +achromatic +acquis +actionanimated +activitybased +actomyosin +acutirostris +adenomatous +adjudicative +adlibbed +administrate +adversities +aedes +aegagrus +aegypti +aenescens +aeri +aeroengines +affray +aftersales +agnathans +airforce +airstable +alBayt +alSadiq +albipennis +albopunctata +albumen +alces +alexandrae +aliciae +alkalis +alkalitolerant +allantois +allcolor +allene +allotropes +allrounders +alnifolia +alreadyexisting +alternations +amazonicus +ameiva +amortized +anarchosyndicalism +anatolica +andrei +angusticeps +annalist +annandalei +annularity +annuli +annus +anomalus +anoxia +ansa +antidemocratic +antifouling +antiparty +antirheumatic +antisolar +antitax +antituberculosis +anvils +apache +apicata +aponeurotic +appointive +aptera +ardens +arenafootball +arginineserinerich +armiger +armillary +aroa +arrowshaped +artfully +artificiality +ascariasis +ascendency +ascribing +ashgray +asis +aspartyl +assignee +assuage +asterisks +atlantic +attitudinal +attractants +audacity +auricollis +autapomorphies +authenticates +authenticator +autoregulation +autostereogram +avidly +avoidant +awardwinner +awnings +ayurveda +azygos +backings +bacterias +badgeengineered +baffled +baffling +bailouts +bandoneon +bandwagon +banger +bani +banka +bankroll +baptizing +baremetal +bartering +baseless +basidiomycetes +basilaris +bello +belowground +benchmarked +bennetti +bentgrass +benzylpenicillin +bespectacled +bestofsix +bewildering +bicolour +bigamous +bigleague +biliverdin +billowing +binning +bioarchaeology +biocide +bioidentical +biomimicry +bioprinting +biopsychosocial +biosimilar +biotransformation +birches +bisporus +bitches +bivalent +bivalved +bivouac +blackcollared +blackmailing +blacktail +bladderpod +bleedingheart +blemish +bleomycin +blooded +bluecolored +blueribbon +blueviolet +bluffing +boatbuilder +bodyshell +boilermaker +bolivianus +bollard +bonariensis +bondsman +bookended +bookie +boombox +booter +booze +borderland +boreale +boric +boride +borings +bosss +bovines +braising +breuningi +bridleways +brightened +brightening +bro +broached +brocket +bronchiolitis +bronchus +bronzeback +brothersinlaw +brushtail +buffets +buffing +bullpens +bullring +bungling +burchellii +burrito +bursae +butchered +butene +buttoned +buttstock +buxifolia +buyrate +caatinga +caffeic +calamine +calamus +calcicolous +calibrating +callable +calligraphers +calliope +canadienne +canter +canticles +cantopop +capite +capos +carbody +carbonized +carboxylase +carburettor +carob +carriercapable +carrierneutral +carting +cartridgebased +castaneum +casted +casteism +catecholamine +catharsis +catkin +catus +caudalis +caulking +cayenne +ccTLDs +cds +ceaseanddesist +celecoxib +cellspecific +cementation +centralhall +centrioles +centripetal +ceratopsians +ceratopsid +chachalaca +chainlink +chanterelle +chatterbot +chattering +chelicerates +chemiluminescence +chilly +chloris +cholangitis +choli +chopsticks +chum +ciclosporin +cinnamomeus +cist +citadels +citrinum +civilizing +clasped +clausa +clenched +cleverness +cliffside +clindamycin +clinids +clitellum +cloacal +clotted +clubbers +clumsiness +coalbearing +coauthorship +coccidiostat +cocoordinator +codirect +cognitivism +coincidences +colchicine +coleaders +coliform +colilargo +collectivism +collectivities +collectivization +collides +colonias +colorists +colossus +commelinids +committal +communions +commutator +compressions +concentrica +conceptualizes +condensates +confound +conjunto +conjures +consanguineous +consensusbased +constricts +constructible +consulategeneral +contemptuous +contented +contestable +continuoustime +contrastive +convergently +cooccurring +coomani +cordatum +corf +costumer +counterfeiters +counternarcotics +cowbell +cowherds +crackling +crass +crassum +creameries +credibly +crenatus +cretacea +crewing +cribrata +cribriform +cricothyroid +crier +crimemystery +crombec +crossChannel +crossreferenced +cruenta +crumb +crura +crutch +crypta +cryptica +cubed +cucurbit +cueing +cultivator +cunnilingus +curbed +curcumin +currentaffairs +cursory +cushionlike +cutoffs +cyberculture +cyclins +cyclopentadienyl +cyclopropane +cylindricollis +cypsela +dAragona +dArtagnan +dacite +damning +dampened +dampness +dancebased +dancelike +dangle +datamining +debarred +decisis +defacing +defamed +defensa +deflate +dehumanizing +dehydrogenation +deliciosa +dellArte +demandside +demarcating +demethylation +demogroup +demyelination +denaturing +deniers +denotational +deontological +deorbited +departement +deplete +dermatan +derricks +desiccant +despicable +despot +detaches +detections +deviceindependent +devolve +dhoti +dialogic +diana +diarrheal +dich +diethylamide +diisocyanate +dimensionless +dimmed +dingoes +dinucleotides +diplomate +dirigible +discolouration +discretetime +disgraceful +dishs +disinhibition +disintegrates +disputation +disruptors +distills +diversely +diwan +dodgy +dogfaced +dolostone +dolphinariums +dominicensis +doodle +doorbells +doublecollared +doubleleaf +downpour +dragoon +drawbar +dreadnoughts +dredgers +dressmaking +droid +droit +drowsy +druids +drumkit +drumline +dualsport +ducting +dudleya +duple +dysregulated +eSport +earlycareer +earlymids +earnestly +eastfacing +eastnortheastern +edX +editio +egganddart +eggersii +eightieth +eightstring +eightyfifth +eine +ejaculate +elbowed +electrify +electroencephalogram +electronbeam +elephantfish +eleventime +eliminationstyle +ellipticum +elopement +emanation +embeddings +emigre +emulsified +endoparasite +endopeptidases +endophytica +endosiphuncular +endothermic +endotoxin +enewsletter +enfant +enshrining +entailment +enterpriselevel +epauletted +ephedra +eps +erections +erects +eremomela +eryngo +erythropoiesis +escapist +esp +esquire +ethidium +ethnocentric +eugenic +eulogized +evodevo +exaggeratedly +exaggerates +exaggerations +exasperated +excipient +excreting +executivelevel +exhilarating +expectancies +experimentalism +expungement +extenders +extinguishment +extravasation +extravascular +extricate +extruding +facelifts +faceon +factorlike +faecalis +falconer +falconers +falx +familybased +fancier +farmtotable +fasces +fatherdaughter +fatherinlaws +fearlessness +ferrugineum +ferulic +fetid +feverish +fez +fifer +filifera +filipes +fimbry +firstaid +fishmongers +fishtail +fistulae +fivealbum +fivecar +flanging +flatbill +flatrate +flavicosta +flavofasciata +fleshcolored +flexural +floorless +floorspace +floury +flowerpot +fluorinating +fluoroacetate +flyway +foie +foliate +folktronica +foment +foolishly +foolproof +footoperated +forebear +forensically +forestay +foretell +forgetfulness +forsaken +fortunei +fourchannel +fourline +fourmovement +fourpetalled +fourstring +fourthround +fourwire +foxhunting +framer +fraudsters +frayed +frazioni +freeaccess +freeopen +freeradical +freethinking +frequencydivision +frontages +frontgabled +frontrunning +fruitworm +fruticosus +ftp +fulvipes +fumarolic +fumitory +fungible +funiculars +funiculata +furva +gaffes +gaiters +galactosemia +gallicus +gamepads +gangways +gars +garu +gasphase +gastroparesis +gazetteers +gelatinase +genealogically +genoa +genotoxic +gentamicin +geodynamics +geoid +germane +giesberti +gimlet +gladiolus +glaring +glasswing +glides +globule +glowworms +gluconate +glycopeptide +goldrush +gracilior +graffito +grammy +grasps +gravitate +grayhooded +graziers +greenblue +greenstriped +grenadine +grimoires +grisaille +grossers +grotesques +groundstroke +growthpromoting +gueststar +gueststars +gulch +gulper +gundog +gunports +gynoecium +haberdashery +haemolymph +haggis +halfamile +halfamillion +halfbeaks +halogenation +halophytes +halophytic +handaxes +handprinted +hangul +hardnosed +harks +hashed +haystack +headpiece +heartless +hebe +heddle +hedgerow +heifer +heirapparent +heirlooms +hemi +hemispingus +herbacea +herbrich +hesitated +hexachlorocyclohexane +hexamer +hexastyle +hiatuses +highbypass +highestincome +highexplosive +highlow +highorder +highpaying +hinterlands +hipsters +hirsutism +historyfantasy +hobbit +hochstetteri +hoenei +hofjes +hollowbody +holmium +holoparasitic +homebrewing +homefield +homeopath +homespun +hooklike +hopedfor +hornwort +hostparasite +howdeni +humidifiers +hurtful +hyanggyo +hydnoid +hydriai +hydrologists +hydrolysate +hydrophobicity +hydroplanes +hydrops +hydroxylases +hypanthium +hypertrichosis +hypotensive +iWork +ideographs +idled +idly +iliopectineal +illegitimately +illiquid +illtempered +imago +imitatrix +immanent +immitis +immunoassay +immunologically +imode +impetigo +impinge +importin +impotent +impoverishment +impressionable +improvisatory +inXile +inasmuch +inaudible +inauthentic +incongruity +incremented +inculcating +indenting +indexer +indexicality +inducts +indulgent +inequitable +infernal +inferno +informationprocessing +informationsharing +infringer +ing +inheritable +initializing +instagram +interactor +interatomic +interestbased +interlocutory +interneuron +intramembrane +intronic +invagination +iptables +ireland +irreligion +irroratus +isoflavones +iterator +jackknife +jai +jamband +jazzier +jerkinhead +jerks +jilted +jonesi +junco +junipers +junkie +justiciable +justifiably +juxtapositions +kelloggii +keratinized +khel +kinescopes +kinglet +kitbuilt +kitplane +kitty +kronor +krypton +kwela +laborsaving +labrisomid +ladle +lakeshores +lampposts +lanceolatus +langurs +lanyards +largearea +largeeared +largesize +largetooth +lateness +lateterm +latifascia +latifasciata +lauding +launderette +lea +leadzinc +leafmining +leastaltered +leche +lecherous +legato +lentic +lesslethal +letterbox +lewisia +libations +liberalarts +limitedproduction +limitedtime +limosa +limped +linchpin +lindleyana +liquidpropellant +lithia +lithophytic +lithospheric +liturata +liturgist +livelier +loblolly +longplay +longshoremen +longula +longwhiskered +lossoffunction +lotor +lowmaintenance +lowslung +lowwater +lucifer +lumpsum +lunchbox +lycopene +lymphangitis +lymphoproliferative +lyrata +lyretail +mHz +macadamia +machadoi +macroalgae +macronucleus +magisterium +magnificence +magnificently +mainshock +makebelieve +malabarica +malapportionment +malemale +malkoha +mammallike +manipurensis +maniraptoran +mannagrass +mannitol +mansa +mantelpiece +manytomany +mappers +marabi +marcescens +marquees +martensite +mas +masochism +matchdays +matorral +meandered +meerkat +meetinghouses +megaspores +melam +melo +melongena +membranespanning +memoria +memoriam +menonly +menorah +mercantilism +meritocracy +meshwork +mesonephric +metacarpals +metaheuristic +metaheuristics +metahuman +metalbased +metaleuca +metatherians +meterhigh +metformin +methanogen +methemoglobin +metoclopramide +metricus +micra +microbicides +microcontinent +microfabrication +microfilaments +microfilms +micron +microphthalmia +microspores +microstock +midcourse +mig +milkbased +mima +mince +miniepisodes +minifigures +minke +miraguama +miscreants +mississippiensis +mistranslation +mitra +moccasins +mochi +mockups +mohawk +molecularly +monachus +monilifera +monkeypox +monodrama +monoid +mononucleosis +monorails +mopane +mops +morphometric +morse +mortgagor +mostpopulous +mote +motorcars +mountainbike +mourner +mousedeer +mouseear +muchloved +muddled +mudra +muds +mugwort +multiauthor +multiband +multibyte +multifuel +multiinstitutional +multilateralism +multilocation +multimodality +multinucleate +multipage +multiplane +multituberculates +multitudes +mundo +mundus +muroid +muscovite +musicale +musictheater +muskeg +mustang +mutata +muticus +myTouch +myelitis +myelofibrosis +myocyte +myofibrils +mystica +naiad +nailtail +naira +nakedbacked +naltrexone +nameboard +nameday +nameship +nanosecond +nanowire +nappes +natalis +navale +ndimensional +nearctic +necking +nella +neomycin +neurocranium +neurologically +neuroses +nevi +newel +ngoni +nidulans +nightlight +nigrofasciata +nigrolineata +ninehour +nipalensis +nn +nociceptors +nonRoman +noncollegiate +noncommunist +nondairy +nondemocratic +nonelectric +nonet +nonexperts +nonliteral +nonmetals +nonobjective +nonparole +nonphotosynthetic +nonprescription +nonrevenue +nonsworn +nontariff +noreaster +northwestwards +nuptials +nutcrackers +oberthueri +obeys +occultus +octamer +october +oddson +oecomys +offhand +offhook +officebased +offlicense +offpremises +offyear +oito +oligopoly +olivary +onboarding +oncourse +oneline +oneoverone +onewin +oneword +onramp +onychomycosis +openmic +openwork +ophthalmological +opima +opine +opining +opportunist +oppositifolia +orach +ordinaries +organogenesis +organolithium +otolaryngologist +outgassing +outlays +outofbody +outrageously +outranks +outsize +outskirt +overexposure +overnights +overprotective +overreaching +overshadow +overshadowing +oversimplified +oviduct +oxidizers +oxlip +oxycodone +palecolored +pallasii +palmatus +palmettes +paltry +panicgrass +panty +parasitise +paraswimmer +pareddown +parotoid +parsimony +partes +partiality +partials +passivity +pastern +patriation +patrimonial +pauciflorum +pdr +pearling +peatland +peddler +pedigreed +pellicle +pelycosaurs +penitents +pennames +pennsylvanicus +pennyroyal +pentode +perfectionism +perfluorinated +perforators +perianths +periastron +perishables +perk +peruana +pervaded +petri +pfeifferi +phantoms +phenom +phi +pholidoskepian +phosphorescent +phosphotyrosine +photodiodes +photophobia +photorealism +photosphere +pic +pictograph +pigweed +pilastered +pinheyi +pinholes +pinkishred +pinky +pinnace +pinniped +pinpointing +pisco +piss +pittieri +placidus +plaice +planform +playthrough +playtime +plugandplay +pneumoconiosis +pocketed +poeciliids +pointedarch +pointofcare +pol +polemicist +polkas +polluters +polyolefin +polyols +polyomaviruses +polypores +polyurethanes +porosus +portalThe +portrayer +posek +postandbeam +potentialities +potentiometric +poultice +pow +powerlines +practicalities +praiseworthy +prefabrication +preimplantation +premedical +premonition +prenursery +preparative +pres +presbyter +presumptively +prevertebral +proAmerican +proanthocyanidins +processual +procollagen +procurators +prodrop +productservice +profeminist +prologs +pronounces +propitiate +prospering +proteinases +proteolytically +protonation +pseudepigraphical +pseudoscorpion +psyllids +ptype +pubescence +publicdomain +puffbirds +pulchrum +pullover +pullus +pulmonic +pulped +punctuality +punctuate +punctures +punkmetal +purinoceptor +purplespotted +purpletipped +pushers +pustulatus +putters +pygmies +pyrin +qawwali +qi +quadricollis +qualityoflife +quanta +quilted +quinquennial +quips +racecard +racetam +racino +radioligand +radiotelevision +railgun +rajput +rapax +rapcore +ratcheting +rationalizing +ravenous +realityTV +reappraised +rearadmiral +rebased +recharted +recherche +reciprocally +recommissioning +recompiling +reconditioned +recouped +redistributable +redlined +redpurple +redspot +redtoothed +reducer +reducers +reedition +reflexum +refloat +reformists +refreshingly +refutes +regicide +regrant +regretting +reinforcers +reinterpret +relievers +renounces +reoffending +replicable +replying +repressions +reptans +resent +resupplied +retcon +retitling +retorting +retrievers +reuteri +revaluation +revascularization +rhyolitic +ribonucleotides +riche +rightleaning +rightsholders +rinds +ringside +riprap +roX +roadtrip +rockslide +romana +roomed +ropelike +roper +rosecolored +rotund +roxburghii +rsync +rubiginosus +rubriceps +ruderal +rufifrons +rufousbacked +rufum +sabot +sabotaging +saccule +sachet +sacris +saddlebag +saleable +salicifolia +salicina +salinities +samegender +samplereturn +sanded +sandwicensis +sanitization +sarcomere +sarcomeres +sarsen +sate +saturata +saucy +sawyer +saxophonistmultiinstrumentalist +scaleup +scammer +scandalized +scapulae +scenarist +scenographer +scenography +schooldays +schoolwide +scintillans +scintillating +scot +scratchy +scrawled +seafoods +seagull +secondo +secondranked +secondrun +segmenting +seiner +selfdefeating +selflove +selfpropagating +sellouts +semielliptical +semienclosed +semiprofessionally +semiskilled +sengi +senseless +sensitively +sensitizing +septs +sequelae +seral +serendipitously +seriata +seriatus +sericeum +serow +servicelevel +servile +ses +sesamoid +seventhday +seventhhighest +seventyseventh +sexpunctata +shambles +shamed +shareduse +shareholdings +sharpest +sharps +shawm +sheetlike +shi +shiplap +shophouses +shrewlike +shutoff +sicula +sidewheeler +signmanual +silverspot +simultaneity +simus +singlelayer +singlemode +sinusoids +sistership +sixdigit +sixfigure +sixperson +sixtysixth +sixway +sketchbooks +skuas +skylarks +slashes +slatecovered +slickheads +slipcased +slowmotion +slowworms +slugger +smelled +snaffle +snares +snarling +snatches +sneaks +socialcultural +socialeconomic +socialis +sociolinguist +soggy +soir +solemnis +solum +solvated +sonars +songcraft +sono +sortingassociated +souks +southsoutheastern +southsouthwestern +spacings +spadeshaped +sparred +spayed +spearmint +specifiers +spigot +spironolactone +spitz +splinting +spongiform +sporocarps +spurfowl +spurius +squill +ssRNA +stagehand +staking +stalkeyed +stammering +standardsized +standpoints +starlets +stationer +stearate +steeping +steeples +steinbachi +stemlike +stepwells +stillstanding +stirrups +stoked +stomatal +stonechat +stonemasonry +storable +stovepipe +strada +stragglers +streamliners +streptococcus +strummed +stubbornly +studenttoteacher +studious +stuffs +subbituminous +subcircular +subclans +subcoastal +subcutaneously +subdominant +subdorsal +subjectspecific +suboccipital +subprovince +subsumes +subtracts +subtyping +suffices +sulfone +sultanas +summerhouse +sunlike +superbantamweight +supercarriers +superluminal +superresolution +supersoldier +superspy +superstring +supersymmetric +superweapon +supraventricular +sureties +suspender +sutural +sweetly +swindler +symbioses +sympathize +sympatrically +synergistically +synthetics +tabling +taillight +talon +tamperresistant +tankette +tapas +tariqa +tarsiers +tarts +tasters +tautomer +teachinglearning +teat +tecta +telecommuting +telemarketers +teleosts +telephoned +telephonic +telerehabilitation +televangelism +tentaculata +tenweek +teratomas +terminer +terpenes +terrors +teshuva +tessellatus +testudo +tetrafluoride +thalli +thana +themebased +thenformer +thenyearold +theodicy +theorbo +thinline +thinners +thoughtless +thousandth +threadleg +threebody +threefin +threeinarow +threelegged +threelined +threesport +threestrikes +threewicket +ththcentury +tidally +tigrinus +timekeepers +timeshared +timeshares +tinder +tipi +tirade +tirthankara +tis +titans +toadfishes +toasts +toothpastes +toric +tormenting +tortious +torturers +touristoriented +tourneys +towered +toxicologists +toxinantitoxin +tracheae +trachyte +trackmaker +tractable +tradesperson +trancelike +transactivation +transborder +transcriber +transgene +transglutaminase +translocon +transphobic +treasonable +treasonous +treed +triamcinolone +triandra +triangulated +tribus +tridentate +triiodothyronine +trill +trillions +tripoints +trite +trivittatus +troopships +tropicbird +tropicbirds +truncus +tubercules +tuberculous +tupelo +turgida +turntablists +turrita +tussle +tussocks +tweens +twelvetime +twirler +twobook +twobuilding +twocolored +tworecord +twospot +twotoone +typecasting +typein +typesafe +typica +ulMulk +ulkei +ultraconservative +ultrafiltration +ultramodern +unavoidably +unblemished +uncleared +uncontacted +uncoupled +underclassmen +undercuts +underdrawing +underpaid +underreporting +underwoodi +undyed +unica +unicycles +uninstall +unjustifiable +unkempt +unmanageable +unmoved +unobtainable +unter +untraditional +unveils +unwary +uptotheminute +uranyl +urticating +userlevel +usermode +utilitys +vaporize +velutinum +vendorspecific +ventilate +verecunda +verisimilitude +vermiculata +vicargeneral +vicepresidency +vicissitudes +videocassettes +viruss +visage +visors +vitreus +viverrini +vivipara +vocalinstrumental +vocalize +vodkas +voyaged +vu +wahoo +walkaround +wapiti +wartlike +waterlogging +waterman +waterplantain +waterspouts +wateruse +waveinfluenced +wavelets +wayfinding +waypoints +weighty +welltolerated +wellunderstood +westerlies +westernization +wheelbases +wheelie +whipworm +whisk +whispers +whitecolored +whiteonly +whiter +wholes +wicks +widelyused +wideopen +williamsoni +wily +windbreak +winddriven +windingup +windpipe +wingers +winking +wintry +wollastoni +wombats +woodbased +woodrotting +woogie +workaholic +workerowned +workforces +workin +xylanase +yearslong +yellowfish +yellowishorange +yellowstriped +yews +yurts +zeroknowledge +zerosum +ziczac +zikani +zincfinger +zoogeographic +zoonosis +zouave +abaxial +abducens +abhorrent +abiogenesis +absurdly +acaciae +acalyptrate +accentuating +accentuation +accessioned +acerosa +acetylacetonate +acharya +achondroplasia +acicularis +acinar +acrylate +actinium +actionbased +acupuncturist +acutissima +adCenter +adduced +adits +adsorb +adustus +adventurepuzzle +adzes +aenigma +aeronomy +aerotolerant +aestivum +aestuarii +afterburner +afterparty +aggravate +aggravation +agrili +agroecology +airmobile +airraid +airsensitive +aisled +akLaff +alGaddafi +alKhattab +alQaedas +alZarqawi +alb +albendazole +albitarsis +alders +ali +alkaliphilus +alkalosis +allChristmas +allacoustic +allergenic +allocators +allophones +allot +alphaamylase +altus +amacrine +amanda +amaretto +amateurish +amazoncom +ambitus +amination +amnesic +amphotericin +amply +ancillaries +andinus +anecdotally +anechoic +anovulation +anteroposterior +antheridia +anthos +anthracinus +anticult +antiepileptic +antiimperialism +antimiscegenation +antimonide +antiphon +antiseptics +aperiodic +apid +aplocheilid +apomorphic +apomorphies +apophysis +apostate +apotropaic +appetizing +applicative +april +apsidal +aptitudes +aquaponics +aquaticum +arbors +archicortex +archipelagoes +arecanut +arguta +armors +armslength +arpeggiator +arpeggio +arrestor +arrowwood +arthrogryposis +ascenders +ascetical +ascomata +ascription +associativity +asters +asthmatic +asylumseekers +atkinsoni +atrazine +attenuating +aucklandica +audiocassette +augmentative +augusta +auras +auricularia +aurulenta +australiana +authorisations +auxilia +bTV +babirusa +babu +babul +backlighting +backplanes +backscattering +bada +bailo +bairro +bakes +balearica +baler +ballooned +bambusae +bane +bankable +bara +barbells +barebones +barrelvaulted +barrierfree +baryonic +basionym +bassplayer +bather +bathymetry +batlike +bayed +bazooka +beachheads +beatmaker +befallen +begat +belied +bemoaned +benghalensis +benzoyl +beryl +bestiary +bestkept +bestowal +betacoronavirus +betasandwich +betasheets +beware +bezoar +bifrons +bifurcates +biguttata +bilaterian +bilinealis +bilinguals +binotatus +bioanalytical +biomorphic +biophotonics +biopolymer +biotopes +birefringent +bisector +bist +bitComposer +blackfin +blacksnakeroot +blanched +blanketed +bln +bloodletting +blowflies +bluecapped +blunted +boastful +boatswains +bobby +bobsleighskeleton +boissieri +bologna +bomblet +bomblets +bonebed +bonito +bonobo +bookbinders +bookers +bookmobiles +bootstrapped +bordello +boronic +botfly +bottlings +brachytherapy +brachyurus +braggadocios +braunii +braziliensis +breadcrumbs +breading +brevicaudata +brokenhearted +bromodomain +brotherly +broths +bryophyte +buchneri +bucki +bugler +bugles +bugloss +buildout +bulldogs +buntlines +buprestid +burrfish +burtoni +bushcrickets +bushfrog +businesslike +butanol +buzzword +buzzy +cabinetmakers +cackling +caecum +caeni +caesius +calcicola +calicoflower +caliginosus +callosa +campfires +campuss +canaliculatus +cancellous +cancercausing +candidatus +candoecon +caninum +cantellation +canthus +cantilevers +capitula +capitulum +capixaba +cappuccino +carbanion +carcinoembryonic +carpel +carpeted +carrierborne +carteri +carves +casecontrol +casemakers +castmember +castrato +catappa +catechisms +categorizations +caudatum +cavernosum +celebritys +cenotaphs +centenarian +centralpassage +centrism +centrists +centrosomes +centrum +centuryThis +cervina +chairback +chalcogenide +chalkboard +chandler +chapmani +charango +charlatan +chartings +chatrooms +checkerspots +checkup +chervil +chestnutcapped +chestnuttailed +chevrolati +chewable +chikungunya +childcentered +chinook +chironomid +chloral +chloro +chore +christmas +chrominance +chugging +ciguatera +cinchona +cinders +circuitswitched +cirrostratus +citrinus +citydwellers +civets +classicists +classless +clavicornis +cleats +cleavages +clevelandii +clevis +clickers +climatologists +clings +closedcanopy +clothe +clownfish +clownfishes +clozapine +clumpforming +coachwork +codevelopment +coelestis +cogenerate +cognizant +cohesin +coldness +collagist +colliders +collinsi +colluding +cologne +colonialist +colonialstyle +colorization +combinational +commonlyused +communitarian +communityminded +communitywide +compacting +compensators +compostable +computerization +conclaves +condensedmatter +conferment +congestus +congoana +conjoint +conjugations +connexa +conscientiousness +consignments +constitutionalist +constraintbased +conte +contemporarily +contortionists +contractus +contravene +contre +conundrum +conures +convalescence +convertibility +convexus +conveyancer +coolie +cooper +coopt +copresent +copyists +copyprotection +copyrightable +coquilletti +cordite +cornbread +corniculata +cornified +cornu +corporals +corroborate +corydalis +cosmologies +costlier +counterargument +counterfeiter +counterforce +cowcalf +cowpox +crabgrass +craftsperson +craniosynostosis +credulous +creeps +crevasses +cribbage +cribricollis +cringe +crispus +crock +crofts +cronyism +crosscity +crossdressers +crossflow +crosshairs +crossindustry +crosslinguistic +crossmedia +crosspromotion +crunching +cryogenically +cryogenics +cryptid +cryptographers +crystallizing +cubicles +culturebound +cumini +cuneate +curassows +curried +cursors +curvatures +cwm +cyanosis +cyclamen +cyclophosphamide +cypionate +cyprinids +cytosines +dAdda +dEspoir +dHuez +dOrcia +daguerreotypes +dalits +dappled +darshan +databank +datacentric +datastores +decapods +deceives +decemvir +decipherment +decolor +deconstructing +deconvolution +deepbodied +delightfully +delocalized +deltaic +denarius +dendrobium +denigrated +dents +depolymerization +deregulate +derivates +dermatomes +dermatomyositis +dermatophytes +describers +desmosomes +destino +detaching +deubiquitinating +dev +dexterous +dhole +diagenesis +diavolo +dichroism +dichromatic +difformis +difluoride +digenetic +digibook +dihydropyridine +dilapidation +dimensionally +dingo +dinosaurian +directcurrent +dis +disappoint +disappointingly +disbarment +discectomy +disconnects +diskjockey +dispassionate +dispensations +dispersant +dispersants +disputable +dissorophoidean +distributable +districtlevel +divina +diviner +documentarians +documentarymaker +doe +dogleg +dogtooth +dolphinarium +domestique +donovani +doublebill +doubleheaders +doublevinyl +douc +dovetails +downhome +downlisted +dpi +draco +drafter +dragsters +dreampop +dredges +drizzle +droll +druidic +drumbeat +drumheads +duPontColumbia +dualfuel +duespaying +duetted +dustbin +dysostosis +dysplastic +dystrophic +eIFG +eMule +eScience +eXtended +earlobe +earnt +earthcreeper +eavesdrop +ebola +echinus +ecovillage +edgetoedge +editorialist +editorpublisher +egregia +ehrlichiosis +eightysecond +eins +elapse +electioneering +electrodiesel +electropositive +electrostatics +electroweak +elevenday +elitism +elliottii +embassieshigh +embryologist +emittance +employmentrelated +emptyhanded +enameling +endgames +endoglucanase +endoscopes +endows +endproduct +enduse +enfants +engineerproducer +enjoin +enkephalinase +ensigned +ensnare +entactogenic +entangle +enterocolitis +entrenching +epaper +epaulet +epidermidis +epigrapher +epigraphs +epimer +epineurium +epitomize +equalspan +equid +erFX +erinacea +erratica +ersatz +esterases +ethnologists +eudicot +euphemistically +evanescent +eventuality +ewe +exBritish +exarata +exhortations +exonerate +exostosis +exotics +expandability +explicitness +extendedplay +extensors +extracellularly +extraprovincial +extrastriate +extrude +eyepatch +eyewall +faber +facetransitive +faders +faerie +faeries +fale +fanfares +fantasybased +farwestern +fatigues +featherlegged +fedora +feedbacks +feedin +feldspars +felonious +femurs +fencedin +ferrea +ferro +ferrule +fetishists +fetlock +fibratus +fibrotic +fictionist +fiddleneck +fides +fifthhighestgrossing +figlio +figurae +fijiensis +filicornis +filius +fimbria +finalgame +finchlike +finedining +finergrained +fingertip +finitedimensional +firebombed +firstseeded +fishfly +fishponds +fiveandahalf +fivebook +fivecent +fivedigit +fiveround +flagpoles +flaked +flashforward +flatboat +flatmates +flattered +flavanol +floodlight +floortoceiling +floundered +flowchart +fluoxetine +flyout +folkpunk +folkstyle +fomented +fomenting +fontinalis +fordii +foreseeing +formalizes +formosanus +formosum +forthwith +fossilbearing +foundermember +fourdecade +fournote +foveolatus +frailty +framers +frangible +fraternitys +frenectomy +fresheners +freshwaters +frictionless +fridge +fringefingered +fulgidus +fumarole +fumigatus +functor +funhouse +furtiva +fuscula +gagged +gaijin +gallinaceous +gallinarum +gamechanging +gangliosides +gantries +gavel +gelatine +gemina +gemmae +gemology +generalists +geniculatus +gentiles +gentis +gentryi +genuss +geodesics +geotagging +getrichquick +getters +geyeri +gforces +ghostwriting +gilberti +givenThe +glassmaker +globosum +glucosinolate +gluons +glycerine +glycosylases +glycosylphosphatidylinositol +gnutella +goalline +goalpost +godfathers +goggle +goldencrowned +goodhearted +goons +gov +governmentdebt +gracing +graminearum +grammitid +grandiceps +grandiflorus +grandsire +granodiorite +granti +grantinaid +granulipennis +granulocytic +grasswren +gratuity +graueri +gravedigger +graycrowned +grazes +greenbrier +gridconnected +gripper +groins +groovy +groping +groundhog +guestbook +guillemots +guitarbass +guitaristbassist +gundi +gunfighters +guyanensis +guyot +gyroplane +hadiths +hadron +haematopoietic +haemolytic +haemorrhoidalis +hag +hajj +haka +hakea +haloperidol +hamiltoni +hamstrings +handcranked +handstamp +hantaviruses +hapkido +hardcoremetal +harddriving +hardshelled +haveli +hawkmoths +hawksbeard +hawksbill +hayesi +headlamp +headlong +headwords +healings +heddles +helianthi +heliosphere +helipads +heliskiing +helpfulness +hematoxylin +hemiparesis +heteroatom +heterogametic +hideouts +highconcept +highestprofile +highfunctioning +hippedroof +hiproof +hispidula +histograms +histoplasmosis +hocicudo +homebound +homeschoolers +homophone +homophones +hongkongensis +hoodoos +hornist +horrendous +horrormystery +hort +hotseat +housekeepers +housemaster +humanely +hush +hyaluronidase +hydrogel +hyperalgesia +hyperkeratosis +hyperkinetic +hypermutation +hyperplastic +hyperspace +hypnotism +hypocalcemia +hypogeous +hypomania +hypoparathyroidism +hypostome +iCalendar +iMovie +iPhoneiPod +iRobot +ichnology +iconoclast +iconostasis +icosahedra +igloo +immunemediated +immunosuppressed +impar +imploring +impoundments +impregnate +inbrowser +incanus +incl +inclusivity +incorporators +incriminate +indecora +indigenes +infidels +infinitives +ingests +innovativeness +inoculant +inorder +inotropic +inquirenda +insigne +insolitus +inspace +instantiate +insulates +interactional +intercalating +intercoastal +interlinking +interlobular +intermingle +internalizing +intimated +intoxicants +intraperitoneal +intricatus +invigorating +ipso +irate +islandica +iso +isoenzymes +isothermal +issuebased +italicus +ivermectin +jackalope +jazzoriented +jeepney +jenny +jerking +jesters +jockeying +jolt +joshi +jour +jujitsu +jujube +jumpsuits +junctures +juniorlevel +kachina +kala +kali +kameez +kar +keelboats +keratinocyte +ketal +kevlar +kinesis +kingbird +kingly +kininogen +kinky +kirkii +kleine +koreana +kumaenorum +kylix +labialis +lability +labyrinthodont +lactase +laminitis +landscaper +lanei +languagelearning +languagespecific +langue +lanigera +laparoscopy +lapponicus +largeflower +lasiocarpa +latefasciata +laticornis +laughable +laundered +lauryl +lavage +lawfulness +leagueleading +leantos +lectureships +lepidopterans +leptalea +lettings +leucophaea +leva +lexicographers +lexicographic +liber +lichenicolous +lidded +ligata +lightblue +lightening +lightvessels +lignicola +lilioid +limping +lindheimeri +lindy +lineolatus +linsang +liocichla +lionfish +lipidlowering +lipsynched +lis +loafing +loathed +lobatus +locomotory +locules +logbooks +logperch +london +lonesome +longclawed +longdelayed +lounging +loveable +loveless +lowerelevation +lowturnout +loyalism +lubber +lunaris +lunatics +lunule +lurk +lysed +mAh +mHealth +mTc +macerated +maces +macrodon +macropus +macroscale +madeforcable +madetoorder +madrasahs +magazinefed +magnetoencephalography +magnetoresistance +mainboard +maiolica +maiores +maison +maisonettes +majorityminority +mako +malady +mali +maltings +mandarina +manicures +mannikin +mantellid +mapmaking +margaritae +marginalize +mariana +marietti +martingale +massparticipation +masterly +masterworks +masturbate +materia +matinees +mattered +mb +meaningmaking +meatball +meatloaf +medica +medicolegal +mediofasciata +medulloblastoma +mee +melanosomes +melic +mellower +melodeon +menaced +menorrhagia +mento +mephedrone +meriting +mesocyclone +mesolithic +messes +messing +metaethical +metalled +metalloid +metamorphose +meterlong +methacrylate +methanogens +methodologically +metronidazole +metronome +metropolitans +microasteroid +microcephala +microelectrode +microsite +microsomal +microsurgical +middleoftheroad +midsixties +mille +milliliter +mineralocorticoids +minimalis +minuet +misaligned +miscalculation +miserably +misinterpret +misinterpretations +misleads +miso +misreported +misshapen +mitchellii +mitzvot +mixedtonegative +mk +mockumentaries +modello +mohair +moisturizers +molybdate +mon +monadnock +moniliformis +monnei +monocyte +monocytic +monogyna +monohydrate +monopolizing +monosperma +monstrosa +montrouzieri +monzonite +mooncakes +moron +mothertongue +motorcar +mouflon +mtvU +muckraking +mucocutaneous +mucopolysaccharidosis +mudpuddling +muhafazah +multicasting +multiprocessors +multisided +municipium +murphyi +mushing +musiciancomposer +mustelid +musty +myalgia +mycobacterial +myelination +myosins +myotonic +myspace +mysticus +nanoseconds +naris +nasus +nationalize +nautilids +nautilus +nds +nebulas +needlefly +needlestick +neoNazism +neomexicana +nephrogenic +neststraw +netlist +neuroanatomical +neuroblasts +neuroeconomics +neurogenetics +neuropteran +neurotrophin +newsman +newwave +nigerrima +nightwear +nigroapicalis +nite +nitrides +nitrogenase +noblest +nodulosa +noirs +noiseless +nonCatholics +nonCongress +nonGreek +nonattendance +noncompulsory +nonconfrontational +noncorporate +nondurable +noneconomic +nonfinishers +nonimport +nonindependent +noninterventionism +nonjudgmental +nonlegal +nonliturgical +nonnational +nonperformance +nonplacental +nonplayers +nonpotable +nonpowered +nonprimary +nonproteinogenic +nonresponse +nonribosomal +nonscripted +nonspore +nontour +nook +normalis +northtosouth +notifiable +novads +novaezealandiae +novarum +novena +nucleobases +nudism +nunneries +nutraceutical +nylons +obscurata +obtusirostris +obviating +occiput +octocorals +octuplets +od +oddest +offaxis +offstream +offtarget +offthewall +oldestknown +oleoresin +oligarch +oligarchic +oligomer +ommatidium +omnipresence +oncoprotein +oneoffs +onlinebased +oogonia +oospecies +opercular +opercularis +ophthalmia +ophthalmosaurid +opposita +oppress +orangebreasted +ordeals +oreas +ornithomimosaur +orthochoanitic +ostraca +otagoensis +outbid +outgrow +outliving +outofcontrol +outspent +ouvrages +overcall +overhears +overwater +oxidations +oystercatchers +ozonedepleting +paa +padlock +pageviews +paidin +paladin +palaeography +paleocortex +paleoecology +palmaris +palmlike +pandit +pantanal +papaverine +papayas +papuanus +paracanoeist +paraensis +paramountcy +paraphilic +parented +pargana +pari +parquet +parsons +particularism +particularities +parturition +parviceps +passivation +pat +patchily +patency +pater +patera +pathosystem +pathovar +patronym +patruelis +patulin +pawang +payola +payphone +peaceable +peacekeeper +peaceloving +pearsoni +pecel +peddle +penetrators +pennate +pennsylvanica +pent +pentecostal +pentellated +pentyl +peony +perceptually +periallocortex +peristome +permease +perry +persevere +perspicua +pervade +petered +pf +phagosome +pharaonic +phenylketonuria +pheochromocytoma +phosphoenolpyruvate +phosphorylating +photobleaching +photocopies +photocopy +photodetector +photogenic +photoluminescence +photolysis +photosynthesize +phylogeography +physiologists +pianoforte +pianoled +piastra +picnickers +piebald +pigeonhole +piggybacking +piglike +piiri +pinewoods +pinicola +pita +pitchfork +pixies +placentation +plagiarizing +plantarum +plantfeeding +plasmonic +plateway +platinumyear +plumbeus +pointblank +polarize +poliocephalus +polychoron +polylepis +polymerizes +polypody +ponder +postApartheid +postWar +postacute +postconditions +posteriori +postmodernity +postponements +potentiometer +powerassisted +powerboating +poweron +powerup +preEuropean +preKindergarten +prebiotics +prebooked +precode +precongregational +preconstruction +predawn +predebut +predesigned +predestined +preglacial +preissii +premiumrate +preppy +presentable +presentational +prespecified +pressurize +pressurizing +presuming +pricking +priesthoods +primroses +privateequity +privatizations +proTrump +processs +profitmaking +proisocortex +prokaryote +prolegs +propers +prophesy +propraetor +proprius +prorector +prosthodontics +prostituted +proteinaceous +proteintyrosine +protruded +provincelevel +prudential +pseudocode +psychophysiology +pteron +pulla +pulpits +punctatissima +punkpop +punted +purinergic +putu +pyrenaica +pyridoxine +quadrillion +quantile +quart +quarterhour +quartile +quartos +quasiindependent +queenslandica +quickies +quicklime +quizmaster +quod +quoining +quotients +rRNAs +rabbinate +racists +radiationinduced +radiochemistry +ragtag +railbus +raincoats +rajas +rama +rambutan +rancho +rapidity +rapturous +rarefied +ratedetermining +readjustment +reairing +rears +reassuring +rebus +rebuttable +recant +recapitalizations +reconnects +redbanded +redefines +redlist +reelin +refinishing +refits +refresher +regionalisation +regranted +regressions +regularization +regurgitated +rehire +reimagine +reimbursements +reinvigorating +relicta +religiosa +relit +remedying +remembrances +remissions +remover +renames +rentfree +repentant +replayability +reposted +reroute +resentenced +restharrow +restorers +resubmitted +reticent +revisionists +revocable +rewarming +ribonucleoproteins +ricotta +ridleyi +rightward +rimbachii +ringette +ringroad +riparius +riven +roaches +roberti +rockhopper +rockprogressive +rocksnail +rolani +romcom +roomtemperature +rootsy +rosella +rosenbergii +rosids +rostrally +rotundus +rubricollis +rudeness +rufousheaded +runcinated +sabulosus +sailfish +salacious +salat +salmonella +saloonkeeper +saltern +samoensis +sandblasting +sandpipers +sangam +sanitizer +saponin +sarcosine +saundersi +sawtoothed +saxes +saxitoxin +saxophonistflutist +scala +scallion +scalybreasted +scareware +scarification +scaup +scenthound +schists +schizoaffective +schoolrecord +scintigraphy +scitulus +scoparius +scorpionweed +scotoma +screenbased +screenprinting +screwworm +scrubwren +scutellatus +seafarer +seakeeping +seashores +seaters +secondaryschool +secondstring +secondunit +segreto +seismometers +selfarchiving +selfassembled +selfconfessed +selfdiscipline +selfdual +selfexplanatory +selfsustained +sella +semaphores +semihistorical +semperi +sendoff +serena +serf +sesterces +setts +sevenman +sevenpoint +seventhmost +sh +shallowly +shalt +shalwar +sharecropper +sheading +sheepbreeders +shenanigans +shieldshaped +shingling +shiprigged +shipwright +shortestserving +shortrodshaped +shove +showboat +showmen +shunted +siddur +sienna +sigmoidal +signifera +signifiers +silences +silkscreened +similaris +singaporensis +singledigit +singlepiece +singleserving +singlevehicle +siphoned +siphoning +sisorid +sisterhood +sixtyminute +skateparks +skerries +sketchcomedy +skicross +skifield +skirmished +skool +skyrocket +skyway +slapper +sleeker +sleighs +slimming +slinky +slivers +slowdowns +slut +smallbodied +smallholdings +smaragdinus +smartglasses +smokestacks +snailkilling +snorting +snowfalls +soiltransmitted +solenoids +solifuges +solipsism +solus +somethings +sonographers +sororia +sorter +sounder +sourceThe +sparrowhawks +spearmen +specular +spellers +spermathecae +sperms +sphingomyelin +sphingosine +sphinxes +spiderwort +spikenard +splay +splendidus +spolia +spoliation +sporophylls +sporophytes +sportswomen +spotless +springparsley +sprocess +squarekilometer +squaring +squaw +squelch +standardbearers +standins +stanozolol +starrs +startstop +stashed +stateapproved +steamy +stella +stellarator +stenciling +sterications +sternites +sternwheelers +stickwork +stigmatizing +stipitata +stouter +stowage +straggling +straightforwardly +straitjacket +stratiform +stratovolcanoes +stratus +streetscapes +stressrelated +strictus +stringy +stripetail +stroller +structurebased +stupor +stylet +subindex +subking +sublet +sublieutenant +submittal +subsists +substring +subtaxa +subthalamic +sulla +sunroom +supercards +superclusters +superfight +supergiants +superinjunction +superlatives +superordinate +superstes +supplication +suprascapular +supremely +sural +sustainer +swage +swales +swashplate +swathe +sweepoared +sweetsmelling +switchboards +syllabuses +syllogistic +sympodial +synchronic +syncretised +synecdoche +syrupy +taillike +taipan +tamales +tamarins +tamperproof +tangerines +tansy +tapetum +taproots +targa +tarring +teachertraining +teahouse +teamsters +teatime +technicolor +telangiectasia +telcos +templeform +temporality +tempt +tenofovir +tentlike +teratogenic +tertius +testandset +testate +thalami +thalamocortical +theatergoers +theatricality +thenSecretary +thencontemporary +thenfuture +theodolite +thereunder +thermocouple +thgrade +thickeners +thicktailed +thievery +thirdfastest +thixotropic +thminute +tholos +thousandths +threadsnake +threeawn +threecent +threefinger +threepage +threescreen +threespot +throwin +thunbergii +tickers +tickle +timedivision +tlatoani +tomatobased +tonsure +topoisomerases +tormentors +tornal +torquetum +torrid +torturous +totalisator +tourniquet +trachoma +tradeable +tradenames +transcriptomic +transcutaneous +transduced +transpire +travancorica +trebuchet +trenching +tricarboxylic +triclads +trie +trifecta +trifoliata +trills +trimethoprim +trimmers +tripinnate +triquetra +triticale +troff +trompe +tropic +trounced +truncatum +trutta +tryst +tumbled +tumblers +tumbles +turbidites +turbodiesel +twelvehour +twentyfoot +twocent +twofamily +twoliter +typists +tyramine +ubiquitinconjugating +ufologist +ugandensis +ukiyoe +ulema +ultraOrthodox +ultralights +ultranationalist +ultrastructure +ultrazoom +unafraid +unbeatable +uncaring +uncluttered +unconformities +uncoupling +underbanked +underbone +underfoot +undigested +undulate +unearthly +unelectrified +unescorted +unfenced +unfurled +unhurt +uni +uniaxial +unicolorous +unlabeled +unlit +unmetered +unmyelinated +uno +unreformed +unsponsored +unsuitability +untamed +unyielding +upgradable +upped +uppercut +uppersides +ur +ursinus +userland +ut +vCard +valuepriced +vaporizes +vaporizing +variabile +vascularization +vee +veli +verandas +vetchling +viciously +victualler +vignetting +villainy +vining +violenta +virgatum +virginea +virginicus +viridissima +viruslike +viscose +vivisection +vocalized +vocalsguitars +vocoders +volleyballist +vols +volva +vor +vos +voterapproved +waiving +wakerobin +walkovers +wallboard +wallmounted +wampum +warder +wareni +warmseason +warred +warreni +wastewaters +watchmen +waterjet +watermen +waterrepellent +watsonii +wattage +wavelike +wavetable +weaponbased +weatherboards +weaved +wellbeaten +wellprepared +wellwooded +wheelhouse +wheelset +whitestart +wideeyed +widowbird +wigeons +wildrye +windlass +windsurfers +wingmounted +wingsuit +winkle +womanist +wondrous +woodbat +woodsman +wooing +workarounds +worldbuilding +wow +xenoliths +xinjiangensis +xs +xylene +yaxis +yearlings +yellowtipped +yoked +youtube +zIIP +abate +abbreviate +abc +abductors +abetted +abled +ablest +abodes +abortus +absentminded +acaulis +accidentprone +acclimate +acclimation +accreting +acetates +acetophenide +achalasia +acousmatic +acquiesced +acquittals +acrosomal +actionmasala +adamantine +adaptors +addenda +adherens +adit +adlib +aegyptiaca +aeneum +aether +afresh +afzelii +ageappropriate +ageless +ageng +agoraphobic +agricola +agroindustrial +airplayonly +airspaces +alAziz +alIslami +alNuman +albocincta +algeriensis +alii +allAfrican +allegorically +allocator +allogeneic +alloriginal +allyear +alni +alphasynuclein +alternifolia +aluminate +alvars +amanuensis +aminoacyl +ammon +amo +amoebiasis +amphoteric +amylose +anaesthesiology +anaglyph +analogical +analysers +anarchosyndicalist +andean +andicola +andrewesi +andunder +annealed +annulatum +anomalously +anomodont +antediluvian +anthocyanidin +anthropomorphised +antiCastro +antiRoman +antiWestern +antiallergic +antibioticresistant +anticlines +antifascism +antifraud +antigenspecific +antimetabolite +antiperspirant +antiphishing +antisocialist +antithrombotic +antithyroid +antiwhaling +anurans +anxiolytics +apartmentstyle +apertural +appendiculatus +applaud +appraise +appressed +aquila +aquilonia +arboreta +archon +arco +arcshaped +areca +arenosa +arewere +argenteum +aridity +armigera +arowana +arrowgrass +arrowroot +artesunate +artifice +artistproducer +arundinacea +aryepiglottic +asexuality +asneeded +astonishingly +astra +astroparticle +asymmetries +atlantooccipital +atomically +atomization +atypically +audax +audiotape +audiotapes +aulica +auricula +autocephaly +autophosphorylation +autoreactive +autoxidation +awarenessraising +awls +axil +axonemal +bZIP +baccharis +backfires +backtobasics +backwardcompatible +badmintons +baeri +baileys +balalaika +balansae +bambara +bandwidths +bangers +barbarae +barbican +barest +bargained +barkeri +barotrauma +barrestaurant +barroom +baseboard +baserich +bastards +batatas +bathyal +batterrunner +beargrass +beauxarts +beggarticks +behaviorbased +bellcote +beluga +benched +benefactions +bereft +bestreviewed +beststudied +betaamyloid +betacarotene +bezels +bicupola +billfish +bilobed +bioacoustics +biofilter +biomonitoring +bionics +biosystems +bioweapons +bipyramids +birdied +birdies +birdsnest +birdsong +birthname +bispecific +bisphosphonate +bitting +blackhead +blacknose +blackstriped +blackwhite +blairi +blanketing +blitzkrieg +blocklevel +bloodied +blowdown +bluecrowned +blueflowered +boardinghouse +boba +bocagei +bogeyman +bolas +boldest +boobies +booing +bookmarked +booties +boronia +botanique +bottae +bottomless +boxelder +boxtobox +brackishwater +brahmins +brandti +brant +braves +brazed +brazieri +breakins +breathalyzer +breathhold +bricabrac +bricklaying +bridegrooms +bridles +briefer +bristled +brittlegill +broach +broadtailed +bronchiectasis +browncapped +bruneri +brunneum +brutalized +bss +buccaneers +buchanani +bulbuls +bullish +buprenorphine +burdock +burgdorferi +burnishing +burro +businesspersons +buteo +butoh +butters +butterwort +cadetship +caeca +cafeteriastyle +cagelike +calcinosis +caldo +callaloo +calle +callout +candi +candidus +candybar +caniceps +cannabidiol +cantonments +cantorial +canzone +capitulate +capybara +carbocation +carbohydratebinding +carbonyls +cardiogenic +carhop +caribaea +carinatum +carloads +carnosa +caroli +carparks +carrageenan +carted +cartoony +caseworker +castellanus +castellum +castling +castmate +catsharks +caucasian +caveats +caymanensis +cellcycle +cellulosome +centriole +cepa +cerana +chaat +chairmanships +chakra +chakras +chanceries +chanteuse +chapelofease +chapini +charmer +charr +chastised +chatroom +chebula +chelae +chestnutbreasted +chews +chez +chicha +chiefship +chins +chitinase +chits +chlorofluorocarbons +chocolateflavored +chorales +chorizo +chough +chow +chrism +christophi +chthonic +churchmanship +cincture +cinque +citronella +civile +clammy +clamor +clandestina +clapboarded +clarinetists +clarus +classing +claustrum +clearness +cleptoparasitic +clerkship +clerkships +clueless +clung +clypeatus +cnidocytes +coChair +coarranged +coaxed +coccineus +cochairmen +cocounsel +codeword +coeruleus +coffered +cofinancing +cohousing +coinfection +coinventors +coldweather +collegebound +collegeeducated +colli +colonially +comarcas +comedienne +comedywestern +commandery +commissario +commotes +communique +complicata +composted +comprehends +comstocki +concedes +conceptualist +concord +condyloid +confidentially +conformist +confounds +conjugacyclosed +conjugating +conjunctive +conjuring +consultum +consumerdriven +contentions +contextaware +contiguously +conversant +convoluta +cooter +copei +coprolites +copyprotected +corallina +corallites +cordate +cordatus +cored +coregency +coring +corkscrews +cornflower +coronaria +coronatum +corporateowned +corticola +coruns +coscripted +costae +counterclaims +counterdrug +counterproliferation +cowbird +cowon +coxalis +crampons +crassidens +creamyyellow +creatinine +creolization +cribbing +cribellum +cricoarytenoid +crisscrossed +crocodilelike +croesus +crooning +croplands +crossbones +crosscoupling +cru +crucifer +crumbles +cryptocrystalline +cryptoprocessor +crystallites +culm +cumingi +cupping +curdled +curvipes +curvy +cusickii +cutlass +cuvieri +cyanogenic +cyanohydrin +cyanuric +cybersquatting +cyclophilin +cylindrata +cytochalasin +cytological +dArco +dAulnoy +dOrange +daerah +dahlia +damask +dampwood +damselfishes +dancerchoreographer +dar +darkgreen +darkwinged +dasa +databasedriven +dayboarding +daybreak +daytrippers +deWilde +deadball +deafmute +debauched +decanoate +decemviri +decimalisation +decorus +decouples +deductibles +deemphasizing +defibrillators +deflationary +deflectors +deiodinase +delist +demarcates +democratizing +denature +deniability +denotified +deodar +deoxy +deoxyribose +deporting +deprecation +derogatorily +deserta +designee +deuterated +dextrin +diGMP +diastema +diceless +dichotomies +dichrous +diethylene +differentiator +digitalized +dilecta +diluent +diopside +dioxinlike +diphenyl +dir +disciplinarian +discoideus +discriminant +disfranchisement +disinterest +disomy +disparagement +disproving +disrespected +dissectum +distractors +distros +disulfidelinked +dithering +diuresis +diversus +divinorum +divisors +divulged +djinn +dockworkers +doctoring +dodger +dodsonii +dogwoods +dolorosa +domingensis +dominica +donee +donoradvised +doodles +dopants +dorothea +dorsoventral +doublebyte +doublehulled +doublemini +downandout +downonhisluck +downplays +dowsing +dram +dramaadventure +drawstring +drippings +drivable +driveshafts +drivetrains +droids +dromaeosaurids +dross +drucei +drumlins +drunks +drypoint +dualSIM +dualhatted +dualregistered +dualtrack +dubber +dubiosa +ducarmei +duetting +dugong +dumper +dumpers +duoprism +dupe +duplicatus +durational +durophagous +dussumieri +dusters +dv +dyari +dynamos +earmarks +earpiece +easeofuse +easytoread +eatoniellids +eau +ecclesia +ecclesiae +echos +ecolabel +econophysics +ecotypes +ectothermic +edentulous +edibles +efficientmarket +eggbased +eicosanoid +eighthgeneration +eighthhighest +eightthousanders +ejaculatory +elSisi +eland +elatum +electorally +electra +electrochemically +electromagnetically +electronicbased +electrospinning +elevenminute +elopes +eloping +emanations +embodiments +embryophytes +emesis +emiliae +empyema +emus +encapsidation +endblown +endoparasitic +endoribonuclease +endorphins +enigmatica +enmeshed +enoploteuthid +ensnared +enstatite +entablatures +entactogen +enthalpy +entheogenic +enthralled +enumerators +envenomation +envious +enzymelinked +epee +ephrin +epipelagic +equalsized +equational +equatorialis +equi +equilibration +erred +ersguterjunge +erythrocephalus +esculentum +esculentus +esotropia +essayists +estrous +ethnobotanical +eubacteria +euphorbiae +euthanised +eutherians +evaporators +everpopular +everted +evildoers +evisceration +exCIA +exarmy +excitons +excitotoxicity +exedra +exegete +exoffenders +experimentalist +extendedstay +externalizing +extolled +extraembryonic +extraversion +extremal +extrusions +eyelets +faciundis +factchecking +fady +faipule +falsifiable +familiarization +familyoperated +fannish +fanrun +farina +farsighted +fasciculatum +fastacting +fastenings +fattest +federalization +feedlots +femaleoriented +femaletomale +feminisms +fendleri +ferredoxin +ferrooxidans +fetes +feu +fictionthemed +ficus +fieldbus +fifthplaced +fiftyminute +figureofeight +filiforme +filosa +finery +finless +finweight +fireclay +firefights +fireteam +firstdivision +firstlanguage +firststage +fishmonger +fittest +fiveacre +fivesecond +fivesided +fixedsize +flaccida +flashcut +flashsideways +flatscreen +flavirostris +flavivirus +flexes +fluorination +fluorocarbon +fluoropolymers +flutterer +flyable +flytrap +fob +fogou +foley +foliacea +foodplant +foraminiferal +forcipata +forded +forebody +foreclose +foreshadow +forfeitures +forktailed +forlorn +foyers +franciscanus +frankness +frantically +frat +fraternization +freeborn +freedesktoporg +freedomfighter +freephone +freepiston +freerange +frequencydependent +frequentflyer +frondosa +fronttoback +frugality +fruited +fryer +fulldome +fullyear +fulminans +fumigant +funereus +funfairs +funkrock +funneleared +fuoco +furfural +furrowed +fuscomaculata +fuss +futurology +gabapentin +gaffrigged +gambled +gambusia +gamekeepers +gameshows +gametocytes +gapping +garagerock +garfish +garish +gasping +gastrocnemius +gastrovascular +gateposts +gayoriented +ge +gegen +gelded +gemma +gemmology +gendarmes +gennaker +geodesist +geopark +geoscientific +gertschi +gi +gibber +gibbicollis +gilts +ginkgo +girlhood +glabratus +glimpsed +gliomas +glucanohydrolase +glucosinolates +glucuronic +glycerides +glycoconjugates +glyoxylate +gnateater +goalsetting +goanna +goatee +godeffroyi +goo +gorgon +gorgonian +gorgonians +gossiping +gouldii +governmentrelated +gradualism +grail +granatensis +granduncle +granny +granulosum +grappler +grasscovered +grassleaved +grater +gravimetric +graving +graycapped +greased +grieves +gristmills +groovebased +grotei +grottoes +groundlaunched +gt +guacamole +guardrail +gubernaculum +guile +guitarshaped +gulai +gunrunning +guqin +gurdwaras +gusset +gyrocompass +hailstorm +hairiness +halacha +halfstory +halophiles +han +handcrafts +handdug +handicapper +handshapes +handspring +haram +harddrive +hardtoreach +harmonizes +harpists +hassles +hatchetfish +hawkish +headcoach +hearses +heatingcooling +heavyset +heckling +hectocotylus +hectolitres +heelsplitter +hegemon +heiresses +helicis +helvetica +hemiacetal +hemionus +hemipterans +herbology +heroically +hesperid +heterocysts +heterojunction +heterostracans +hexavalent +hexokinase +hibernates +higherpriced +highestdebuting +highestquality +highestrating +highheeled +highlift +highwire +hijra +himalayana +hindrances +hiphops +hispidum +hodgepodge +holidaying +holistically +holsters +homebuyers +homestay +homiletics +homological +homonyms +honeycombs +honourees +hoolock +hornless +horseless +horticulturists +hotshot +hounded +housefly +householder +howl +hubbardi +humaninterest +humeri +humi +hummocks +hummocky +hunterkiller +hurl +hushed +hustling +hyalinus +hydrofluoric +hydrogens +hydrolysable +hydronic +hydroxycoumarin +hydroxylated +hymenoides +hymnody +hypercalcemia +hypercorrection +hyperventilation +hypocaust +hypovolemia +iBiquity +iFM +iVillage +ibid +icefield +icefish +ichnospecies +iheringi +ilex +ilk +illconceived +imbalanced +imines +immunohistochemical +impactor +impartially +impatience +impatient +impedances +impolite +improbably +impulsiveness +inapplicable +incarnate +incomers +inconceivable +inconcert +inconclusively +indefatigable +independentlyowned +indiscipline +indolebased +inducements +industrialelectronic +indwelling +inescapable +inexorably +infidelities +informationbased +ingot +inheritances +innocents +inops +inouei +instep +insulana +insurrectionary +integrable +integrally +intensifiers +intensional +intensivecare +intercollege +interferometers +interiomarginal +interleukins +intermediation +intermixing +interpeduncular +interzone +intraplate +introversion +intuitionism +invalids +inversa +invertible +invicta +involution +ironore +isabella +ischial +isomorphous +isoquant +itemizes +ivorybilled +jacaranda +jackpots +jackrabbits +jamborees +janus +javascript +jean +jelskii +jetblack +jibs +johnstonii +joiners +judgemade +jumpstart +kN +kabuki +kaftan +kaki +kalari +kalimba +kelvin +kempyang +kermadecensis +kerogen +ketene +ketoacidosis +keyboardistguitarist +keystream +khanqah +khat +kho +kinaseassociated +kindling +kip +kiwifruit +kneels +knickers +knobbed +knowitall +kokanee +kraal +krupuk +kt +kurgan +kw +lOrdre +laboratorys +lacerations +laidoff +lambing +lamprophiid +lanceleaf +lancifolia +landliving +laneways +languageindependent +largecaliber +largescreen +lariat +lashing +laterialba +lather +laths +latior +laurae +lawnmowers +laxity +leaden +leadfree +leat +lectotype +legatine +legitimization +legitimizing +leiomyoma +leleupi +lengthens +lengthier +lentus +leotard +leotards +lepidopterist +lessingia +letzte +lib +liberalizing +librations +libri +licensors +lifealtering +lifesaver +lighterweight +liiga +liken +limbal +linesmen +liniment +lipopeptide +liposome +lipsync +lisp +lithographed +lithologies +lithos +litura +liveperformance +livework +lncRNAs +loams +localizer +locallevel +longhandled +longicaudata +longipalpis +lontong +loti +lowrent +luciferin +lucus +lunchtimes +lunula +lusca +lutzi +lycanthropy +lycopersicum +lysyl +maars +mackerels +madre +mags +maguirei +mailin +maire +majalis +malacological +mammaliaform +manakins +mancipi +mandapa +maned +mangabeys +mangosteen +mani +manna +manycore +maquis +marchese +marinades +marketshare +massaging +massproducing +masterfully +mastership +mastic +masturbating +matchpoint +matteroffact +maturities +mbuna +mealworms +mediumship +meetingplace +megachurches +megacity +megakaryocyte +megaloblastic +megaproject +melanite +melilot +melodically +melzeri +memberdriven +membranophones +menhaden +menisci +mensural +mesodermal +mesoglea +mesonephros +mesoporous +metabolomic +metallescens +metalpower +metaobject +metatherian +methylcytosine +metonymically +microbicide +microclimates +microfibrils +microfilariae +microgranular +microphyllum +micros +microsystems +midcap +middleage +midnorthern +midtier +miei +mightiest +mike +mildest +miliolid +milked +millpond +millworkers +mimus +minicamp +minitournament +minty +minutiae +mis +misella +miseries +mishmash +mismanaged +misnamed +misogynist +misprint +moans +modicum +mohan +monazite +moneymaking +monooxygenases +monopropellant +monopulse +moonwalk +mor +mora +morgen +morphinan +mosh +mosquitoborne +mostread +mostrecent +motherly +mothertochild +motionbased +motioncapture +motivators +motorcross +mountainbiking +mowed +mucronatus +mudflows +muesli +mugged +mugging +mullion +multicurrency +multigrain +multihulls +multilink +multimeters +multiphysics +multipleoutput +multiproduct +multisector +multispan +muris +murti +muscorum +muskox +mv +mycelial +mycotic +myelopathy +myofascial +myostatin +mystax +mythologist +myxedema +nOg +nacional +nada +nakedness +nanometerscale +nanometres +nanotechnologies +nanum +nasalis +natricine +naturae +naturallyoccurring +neatness +neckwear +nectarines +nectary +needlessly +negroes +nematocysts +neoclassic +neocolonialism +neosuchian +networkcentric +neumanni +neuroactive +neurochemical +neurosteroid +neverbeforereleased +newberryi +newline +nightshades +nightspot +ninetrack +ninthhighest +ninthmost +nip +nirvana +nitrated +nitrites +nivosa +nogo +nonApple +nonDisney +nonEuclidean +nonGMO +nonNewtonian +nonUK +nonaggressive +nonanimal +nonconfidence +nondefense +nonexpert +nongraduates +nonhistoric +noninstitutional +nonintervention +noninterventionist +nonmalignant +nonmusic +nonpassenger +nonperturbative +nonphysician +nonprofitable +nonpsychoactive +nonrecurring +nonregular +nonrepresentational +nonrotating +nonscience +nonsensemediated +nonspeech +nonterminal +nonthermal +nontowered +nonuniformed +nonunionized +nonwinning +noresult +northerners +northflowing +nothingness +novelistic +nucleoli +nucleon +nuit +nuragic +nymphal +oberthuri +obligates +obscuripennis +observability +observergunner +obstetriciangynecologist +obtrusive +obviate +occultations +oceanarium +octopods +odometer +odoratum +offreserve +offthefield +offtopic +offtrack +ohms +oilcontaminated +okapi +oldworld +oligodendrocyte +olivieri +olympiad +onechild +oneforone +onestep +oneyearold +ontogenetic +oozes +oozing +opalina +opendoor +opentopped +openweight +operability +operationalized +opisthosoma +opsin +orache +orang +organophosphates +organotin +ornithuromorph +oro +orthocerid +oscillated +ospreys +osteology +osteopath +ostracon +otherness +outpacing +overallrankings +overbroad +overestimation +overreaction +overruling +overstayed +overunder +overwhelms +overworld +ovipositors +ovulatory +owstoni +oxidiser +oxychloride +oxyconic +oxyrhynchus +pachycephalosaur +pacificum +paged +pagus +palaearctic +palindromes +pallidipennis +pamoate +panela +pang +paniculatum +pantheistic +papilio +papyrifera +paracyclist +paradoxum +paraprofessionals +parasitises +pareiasaurs +parley +parotta +pascoei +passengermiles +pastas +pastellist +pastrami +pasturage +patellofemoral +patriciate +patronize +patti +paysites +peacefulness +pearlwort +pedantic +pedicure +peltate +penandpaper +pencak +pendens +penetrans +penghulu +penitence +penknife +penstemons +pentalogy +pentameric +pentandra +pepperoni +peppy +perf +perfoliata +perinuclear +periodontist +periodontium +perithecia +permittivity +persimmons +perstans +persuasions +peruanus +perunit +petrologist +phagocyte +phalangeal +pharmacognosy +pharmacologists +philandering +philologists +phlorotannin +phosphotransferase +photobooks +photomasks +photometer +photomontage +phyllostegia +phylogenic +phytochemistry +picipes +pickpockets +picric +pictipes +pietistic +pikes +pilosum +pimples +pinnules +pinworm +pitvipers +placemaking +placers +planche +platani +platethigh +platformadventure +playas +playerdriven +pledgers +plena +plicatus +pluripotency +pointbased +polaritons +policedrama +pollute +polyphase +polyphemus +polyphenolic +polysilicon +polytetrafluoroethylene +ponyfish +popsicle +populares +portalHenry +portlets +postRevolutionary +postminimalist +postpolio +postprandial +postshow +potyvirus +powerhungry +pracharak +pragmatically +pram +prams +prasinus +preIndependence +preNorman +preachings +preconstructed +prefigured +preheat +prehispanic +premenopausal +premiershipdeciding +preprep +presaging +previouslyreleased +pricefixing +printouts +privata +privateuse +proboscidean +proboscideans +professionalgrade +progeria +prolata +promulgates +pronouncer +proofofwork +propolis +propriospinal +prosencephalon +prostration +protean +prounion +provicechancellor +provident +prurigo +psychophysiological +publicinterest +publishereditor +publishsubscribe +pullback +pumilum +pumper +puncta +puppeteering +pur +purgative +purifier +purplishblack +purposive +pusio +pyralid +pyriforme +qua +quadrinotata +quadripunctatus +quam +quasimilitary +quasiperiodic +quicksand +radiopaque +radioplay +radome +rajahs +ranchstyle +rappersinger +rasterization +ratifies +rationibus +rauisuchian +rawness +raze +reacquire +reactance +realitycompetition +reallocate +rearfacing +rearview +reassign +reassortment +reassumed +rebbes +recalcitrant +recapitulates +recedes +receiverdefensive +reciprocate +reckon +recoded +recognizance +recolored +recommission +rectally +rectangulus +rectouterine +rectrices +recyclers +redcarpet +redeal +redoing +redshank +reenlist +referents +reflexively +reflexivity +refract +refugium +regressed +rehearing +reheated +rehouse +rehoused +reification +reignite +relabeled +relinquishment +relives +remanence +renegades +repairer +repatriating +reportable +reputational +requester +requestor +reranked +reread +resentencing +reshuffles +resinosa +resistanceassociated +resoundingly +restocking +resuscitate +retarding +retested +retinoids +retrain +retroreflective +reunify +reverseengineering +rewiring +rhetoricians +rhizosphaerae +rhombifolia +ricebased +rickety +ridgelines +righttowork +ritonavir +roadracing +roadrunner +roared +rockslides +rocksynthpop +romanisation +rookeries +rootless +rosadoi +rosenbergi +rossa +rotogravure +roundels +roundly +roundnecked +roundtrips +roused +routings +rowboat +rowboats +rubblestone +rubicundus +rucksack +rufescent +rufitarsis +rufousbreasted +rufouscrowned +rufousnaped +rufousthroated +rutila +sagar +saka +salinus +sallfly +salva +sanguine +sanitize +sanitizing +saprotrophs +sarawakensis +sarda +sardars +sarong +satellitedelivered +satiation +saucepan +savignyi +scallions +scantilyclad +scapulars +schnapps +sconces +scopulorum +screwdriven +scrubfowl +scullery +sd +sealift +seaming +seawalls +seconddivision +secondgrowth +secondleading +sedativehypnotic +seebohmi +seers +selfadvocacy +selfassemble +selfdeception +selfexecuting +selffertile +selflearning +selfmonitoring +selfperpetuating +selfrun +semicha +semicustom +semioctagonal +semisoft +semitone +semitones +sequitur +serendipity +serpulid +servlets +sesquiterpenes +setaside +settlors +sevenbay +seventeenyear +seventyeighth +seventysecond +severally +sexspecific +seychellensis +shamisen +shapers +shareable +shelfstable +sherbet +shim +shiur +shockingly +shoeing +shoplifter +shuttering +sickles +sideshows +sideview +sig +signora +sika +simmer +simony +singersongwritermultiinstrumentalist +singlecarriageway +singlechannel +singlecore +singlemasted +singlestagetoorbit +sinkings +sip +sirventes +sisterships +siteswap +siva +sixbook +sixpoint +sixthgrade +sixtyninth +sixtyseventh +skylines +slaked +sleaze +sleightofhand +slicker +slop +sluggers +slurred +smallcap +smoothscaled +snags +snailfish +snailfishes +snipes +snob +socialemotional +sodalite +soldierfish +soldiering +solidi +solstitialis +solvation +somatoform +sonority +sonsinlaw +sorption +soulmate +soundbites +spanner +sparkle +sparser +spatulata +speciesism +speedways +spelaea +spelaeus +speleothem +spermatids +spermatophore +spermidine +spicebush +spiralshaped +splat +splendidly +spoked +spooling +spoonerism +sprayers +spraypainted +springbeauty +springflies +squeegee +src +stagflation +stannary +stardust +startlingly +statelessness +statics +steakhouses +steatosis +steelblue +stepfamily +stereospondyl +stevedore +stevedores +stevia +stickiness +stictica +stimulators +stipendiary +stipularis +stnd +stoats +stonethrowing +storerooms +strafed +streaky +streptomycete +stricture +striolatus +strop +structurefunction +stylesheet +stylosa +subcamp +subcastes +subdirectory +subdwarf +subheadings +sublaevis +sublanguage +subsector +subsidiarys +subsidizes +subspeciality +subsuming +subtask +subtehsil +subtler +subtopic +subtributary +suburbanized +subzones +sucralose +sulfation +sundews +superbum +superciliosus +superheaters +superheterodyne +superimposition +supermodels +superphylum +supersaturated +supershow +supra +sus +swatting +sweatshops +sweetest +sweetgrass +swerve +swingometer +swordlike +swordsmen +syllabics +sylvanus +synchromesh +synchronism +synchronizer +syncline +syncs +syndicators +syntagmatic +systemd +taconite +tacrolimus +tafsir +tailbone +tailending +talkers +taming +tapa +tarns +tarp +tarpaulin +tat +tatei +taxfunded +taxol +teals +tearooms +technologys +teepee +teff +teleconference +televangelists +tellall +tennesseensis +tenpiece +tenrecs +tenvolume +terabyte +tercentenary +terrarium +terre +tertia +tesserae +tessitura +testaceous +testretest +tetani +tethers +tetragona +tetrodotoxin +thailandica +theae +theaterintheround +thenChief +thenPremier +thenVice +thenchampion +thendominant +thenfledgling +thenmanager +thenowners +theosophist +thereabouts +thermomechanical +thermowell +thgeneration +thine +thiopental +thirdbiggest +thirdrail +thistletail +thoroughness +thrashers +threedaylong +threedomain +threeheaded +threemeter +threepointers +threewheel +thrombolytic +throughcomposed +tidytips +tiedown +timecritical +timeresolved +titulus +toadheaded +tobogganing +tobyMac +togetherness +toiled +toiling +tonguesole +tonkinea +tonsillitis +toothcarp +toothpicks +tortfeasor +tortilis +tortuosa +totemic +toto +toucanet +touchline +towerhouse +traditionalstyle +trama +tramcar +tranches +translucency +transsexuality +transurethral +trapezius +travesty +treading +trekked +tremble +tremuloides +trenchant +trialist +tribally +tribunate +tricarinata +trifluoromethyl +trimers +trimulcast +tripleA +trogons +tropism +tropomyosin +troubleshooter +truncatula +tuftedtailed +turmoils +turnbyturn +turnings +tweeter +twicedaily +twoandahalfyear +twobanded +twohander +twohundred +twoline +twolobed +twostriped +twowire +typologies +uhleri +ultima +ultradistance +ultramarathoner +ultraprominent +umbilici +unadjusted +unattributed +unbranching +unbundling +uncommercial +uncommitted +uncontaminated +unconverted +undatus +underdiagnosed +underestimation +underfloorengined +underinsured +underwings +undressing +unforeseeable +unhygienic +universityowned +unknowing +unmarketable +unmastered +unmentioned +unpacking +unperformed +unpigmented +unpressurized +unrealised +unrehearsed +unschooling +unscored +unsolvable +untethered +upcountry +uplisted +upperincome +upstanding +urbanites +urbansuburban +urease +urens +ureteric +url +urticae +usenet +userdriven +usurpers +utters +vaccae +vaginitis +valerate +valvata +vancomycinresistant +vandal +varicornis +vectorborne +vedotin +veiny +velomobile +veneered +ventricosus +verapamil +vesicant +vetiver +vici +vicinal +vicinus +victimless +vies +vigas +vinblastine +vindication +vingtaines +virga +virginiensis +virtuosos +vis +visualizer +vitrified +vloggers +vocalistlyricist +voce +volutions +voxelbased +vtm +wad +wagered +waistcoats +wallpaintings +warbird +warbirds +warble +wardrobes +warfighter +wastegate +waterfronts +waterweed +wavyedged +weavings +weberbaueri +weblike +wellcovered +welllit +wellplaced +wellrepresented +westernthemed +westflowing +wheelchairusing +whippet +whitedominated +whitenaped +wickhami +wides +wiggle +wilfully +williams +willistoni +winghalf +winkleri +winks +wolffish +wolfs +woodnymph +woolens +wordprocessing +workability +worldchampion +wracked +wrapup +wrathful +wreaked +wrecker +wrighti +writeoff +writerdirectorproducer +wt +xiphosuran +xxmember +xylanolytic +yangi +yearending +yellowhammer +yeomen +yogis +zVM +zeolite +zygotes +abdita +abditus +abducts +abet +abietis +ablated +abraded +abridgements +abscission +absinthium +abugida +abugidas +acanthodians +acellular +acetals +acnes +acquisitive +acral +acromegaly +acroporid +acta +actio +adamantly +adamsi +addictiveness +adipic +adnexa +adsorbents +advection +advertisingfree +aestiva +aforethought +afromontane +afterdeck +afterload +agonism +agonistantagonist +agoseris +agroecosystem +agroecosystems +airbrakes +airbrushing +aiyl +alAhmar +alArabi +alHashimi +alMutawakkil +alZubayr +alas +alboguttatus +albomarmorata +albrechti +alexia +alighted +alkylated +allay +allcomposite +allimportant +allmusic +allornothing +allwheeldrive +allwomens +allwood +aloes +alps +alwayson +amentum +amethystina +amica +amidohydrolase +aminosteroid +aminoterminal +ammunitions +amnestic +amnicola +amphisbaenian +amplexus +amputate +amseli +amt +anagenesis +anaplasmosis +ancora +angelshark +anglerfishes +anglewings +angulicollis +angustifolius +aniconic +animalderived +animationrelated +animestyle +ankylosaurian +anni +annotates +anonymization +antagonizes +anthesis +anthologys +antiCD +antiLabor +antiSikh +antiausterity +anticapitalism +anticyclonic +antidotes +antidumping +antimateriel +antiobesity +antioquiensis +antipoaching +antipodum +antiprostitution +antipsychiatry +antizyme +antv +anxiously +aphelion +apolipoproteins +appeasing +appendicular +appreciations +aptamer +apterous +aquatilis +aquifolium +arachidis +archaeogenetics +architraves +archiveorg +archosauriforms +areabased +areolatus +aril +arisan +arista +armrests +arraignment +arresters +arteriosclerosis +articulators +artpop +artstyle +asanas +asbestosrelated +ascus +asocial +assaying +assumpsit +asthenosphere +astrobiologist +astronomically +astroturfing +atabeg +atavus +attenuators +attila +attractors +audiological +audiologists +auditable +augurs +aurantiacum +aureum +auripes +autogas +autoloader +automobilia +autoreceptors +autostereoscopic +avex +avialans +awned +axion +azhwars +azoospermia +azurite +bachelorette +backfilled +backless +backstop +bacteriochlorophyll +bagger +bailee +bakso +balfourii +baling +balloonist +bankrolled +barbadensis +barbarous +barbeque +bareeyed +barometers +bartschi +basilicum +basmati +bast +beachgoers +beachwear +bearskin +beata +behaviorally +belaying +bellula +belowgrade +beltfed +benandanti +bender +benthamii +benzamide +berms +bernardi +bestial +betaB +betaglucosidase +betapropeller +bettong +bevels +bhaji +biblically +bibliometrics +bicommunal +bicyclefriendly +biennale +bifidus +bigcity +bihar +bijective +billbug +bioaccumulation +bioculata +biogeochemist +biomineralization +bionanotechnology +bioturbation +bipedalism +birdwatcher +birthweight +bistort +blackbrowed +blackburni +blackcaps +blackchested +bladelike +blading +blanca +blaring +blastema +blockchainbased +blockship +bloodstain +blowbackoperated +blowhole +bluishwhite +boatshaped +bobble +bock +bocourti +bodyboard +bohemians +bola +boliyan +bombardiers +booklouse +bookpublishing +borane +boranes +borg +borides +bowfin +boyar +brachypterus +brachyura +brainspecific +brainstorm +breadmaking +bristlecone +broadshouldered +broadwinged +broilers +bromelain +brotherinlaws +brownishorange +budgerigars +buffthroated +bugeyed +bupropion +burbot +burette +burlesques +buttercream +buxom +buzzwords +cDc +cabooses +cachexia +cadenzas +caduceus +caelatus +caesarian +cajan +calcifications +calida +calmodulinbinding +camelids +canastero +candlelit +canina +canvassers +capitatum +caprock +capucinus +carbolic +cardioversion +cardrooms +carhouse +carnelian +carnosus +carota +carotene +carpetlike +carpometacarpal +carrack +carriergrade +cartoonstyle +casebased +cassiterite +castmates +casus +catabolite +catalepsy +catapulting +catechesis +catenata +cauliflora +cauline +causewayed +cavea +celandine +celata +celer +cellulases +centrale +centromeric +cephalometric +cerebri +ceruminous +cerussite +cervicalis +chaebol +chairmans +chandlers +characterisations +chargesheet +charioteer +chasuble +chatbots +chauffeurs +chaus +chav +cheapness +cheaters +chelator +chemoinformatics +chemoprevention +chiasm +chinch +chinchillas +chiptunes +chiricahuae +chitosan +chlorophenol +chocolatebrown +cholecystectomy +chome +choreographies +christyi +chromatophores +chromogenic +chronostratigraphic +chronostratigraphy +chucks +churns +ciliatum +cinclodes +cinnabarina +cinnamic +citalopram +citrinin +cladistically +classe +clathratus +cleome +clientelism +clinopyroxene +cloakroom +closerange +closers +clubman +cluboriented +cmyc +coaccused +coanchoring +coatis +cocaptains +coccidiosis +cockatiels +cocommissioned +codebooks +codesign +codiscovery +codrove +coelurosaurian +coexpressed +coffeae +cofrontman +cogenerates +coldhardy +coleads +colensoi +collegia +colorcoding +colorimetric +columbarium +columnoriented +comedones +comedyaction +comedyfantasy +comity +commendatory +commercialresidential +commissaries +comorensis +comosus +compensations +componentry +compulsively +conceives +conceptbased +concessional +concoctions +condescending +condolence +condominia +condone +condylar +confectioneries +confessors +confided +config +confiscations +confluences +congdonii +congenitally +conifera +conned +consequentially +consonantal +consumerbased +contactors +contaminates +contemptible +contextualism +contextualize +contextualizing +contortrix +contraindication +convolutional +convolutions +coofficial +cookei +copal +copastor +copperhead +copublishing +cora +coreceptors +coriaceum +corniculatus +corrie +corruptions +corticobulbar +corticotropin +corticotropinreleasing +coscored +cosmopolitanism +costate +costimacula +cottonwoods +coua +couched +coumarins +councilmen +counterclaim +countercult +counterfeited +counterfeits +countershading +countyequivalents +courageously +coveralls +coverart +covermount +coxswains +crepitans +crescentic +cretica +crimeaction +crispata +crispum +croon +crossbars +crossbrowser +crossconnect +crossexamine +crossfunctional +crosshatching +crossreactivity +crownofthorns +cruciferous +crucis +crural +cryotherapy +cryptologic +cryptozoological +cubicle +cubist +culls +curettage +curtainraiser +curvata +cuskeel +custodianship +cutgrass +cyanidin +cyanotic +cybersex +cyclitol +cyclol +cyclopentane +cylindershaped +cyproterone +cyrtocones +cytoprotective +dAzione +dBASE +dElsa +dHarcourt +dItalie +daenggi +dala +dalla +damsels +dangerousness +darkveined +darwiniensis +daytimer +dblock +deLillos +deaconess +decamped +decibel +deckhouse +declarers +declensions +decoherence +decussate +deducing +dee +deemphasize +deepfrying +deflagration +deitys +delator +deliberating +delimiter +dellAquila +dellintervallo +deltoides +deltopectoral +demountable +dene +denervation +denialist +denitrifying +denominate +densiflorum +dentipes +denuded +deorbit +depositor +deprecating +depthfirst +dermatome +desensitized +desertdandelion +destructiveness +devas +devours +deworming +dextran +dhow +diable +diabolica +diaconal +dialectics +dialin +diaphysis +dichroa +diehli +dieoff +diffusers +digitizes +dilatatus +dinein +dioxygen +diphtheriae +directtofan +dirge +disaccharides +discophora +discopop +discretely +disembarkation +disincentive +disobey +dispersa +disruptor +dissections +dissorophoid +distaff +distinctives +disulfides +div +divinatory +divisus +documentaryreality +doer +doldrums +dollhouses +dolosa +domiciliary +dominicanus +dorsiflexion +dosed +dpkg +dragger +drinkable +drinkdriving +drooling +droving +drumset +dryad +dryandra +drywood +dubiously +duckling +dukedoms +duplicator +dvinosaurian +dynamicist +dysplasias +dysprosium +dystrophies +eastsoutheastern +ebXML +ebenus +eberti +eccentrically +echinococcosis +ecolabels +economywide +ecophysiology +ecosocialist +educationalists +ee +eerily +effused +effusions +egovernance +eightbar +eightfoot +eighthgrade +eightsong +ejects +ejournals +elastically +elastics +electronicarock +electronwithdrawing +electrooptic +elegantulus +elementarymiddle +elitelevel +ellagitannins +emarginate +embargoed +emeryi +emigrates +emulsification +enablers +enamine +encrustations +encumbrances +ender +endo +endocrinologists +endophthalmitis +endorsers +enlargers +enormity +enshrine +entranced +entrench +ephemerides +ephippium +epiblast +epicenters +equivalences +erases +erica +eruv +erythrothorax +ethnogenesis +ethnohistory +euptera +ev +eva +eventrelated +everevolving +exCEO +exchairman +excons +exemployees +exfootballer +exguitarist +exhorting +exigent +exiguum +exopolysaccharide +exotoxins +expectorant +explayers +extendedrange +extrabase +extroverted +eyepieces +eyestripe +fabula +facetious +facetiously +faceup +factorbinding +factorybacked +faecium +fairtrade +falconeri +fandoms +fansites +fantasyaction +farefree +farfetched +farinfrared +farmworker +fasciculatus +fasttalking +fava +fecaloral +feebleminded +feign +felid +felting +femtocell +fermentable +fermionic +fernandezi +festooned +fewflowered +fibrosa +fidelis +fifers +fiftholdest +fifthround +filbert +filibustering +filum +finfish +fipple +fireresistance +firethorn +firmament +firstpass +firstpreference +firstprinciples +firth +fistball +fistfight +fitzgeraldii +fivestage +fixedlength +fixedrate +flabellata +flailing +flamboyance +flankers +flasher +flatly +flavolineatus +flavosignata +flayed +flexuous +florensis +floridanum +flowerbeds +fluorspar +flutamide +flyingfish +fody +fogs +folium +folkinspired +folksonomy +footbag +footballsoccer +footballthemed +footstool +forager +foraminiferans +forayed +forego +foremen +forenames +forestation +forhire +fori +formosensis +fortuneteller +forwardline +fourangled +fourcounty +fourinarow +foveolata +foxy +franchiser +fratelli +fraying +freakbeat +freerider +fro +froggatti +frostfree +frosti +fruitpiercing +fuelwood +fufu +fullbacklinebacker +fullface +fullon +fullthickness +fulminant +fulvicollis +fumipennis +furans +gadwall +gagging +galapagensis +galaxiid +gallantly +galleried +galvanometer +gameday +gammaaminobutyric +garciai +garleppi +gasdischarge +gastald +gastroenterologists +gasturbine +gat +gaudichaudii +geisha +gemologist +genderfluid +genderrelated +generales +geneticallyengineered +gentrifying +genuineness +geocaching +geocast +geoglyph +geomatics +geophytes +georgei +geoscientists +geosocial +gestalt +gestroi +gether +ghosting +gigantocellular +gimbal +givers +glasswort +glidepath +gloved +glucosides +glycobiology +gnatcatcher +goa +goaloriented +goddesss +godmani +goldenmantled +goodlooking +goodquality +gopuram +gotta +governmentsanctioned +governmentsupported +graciously +gradesharing +granddaddy +granges +grantfunded +granum +graphbased +graphicsbased +grappa +grapples +gratuities +gravure +graywhite +grazer +greasewood +greatgrandfathers +greenbanded +greenlighted +greenscreen +gregorii +greyscale +griffon +grippers +griseata +grommet +groomers +grossepunctata +groundings +groundup +groupbased +groupies +grunting +guajava +guarana +guestrooms +guitarheavy +guitarled +guitarsvocals +gulp +gunplay +gunsmiths +gurnard +guttulatus +habituated +haematobium +haematoxylin +hailstorms +hairdo +hairstreaks +hairyfooted +hairytailed +halfcourt +halfling +halfsisters +hamate +handbell +handbills +handcraft +handcut +handguard +haptics +harbourfront +harkens +harpoons +hatOLOGY +hatchets +hatter +headboard +healthpromoting +heathy +heatstable +heattreated +hecklers +heeded +heeled +hepatomegaly +heptagonal +heringi +hermeneutical +herpesviruses +hesperiid +hessian +heterodyne +heteronormative +heterosis +heterothallic +heterotypic +heterozygosity +heures +hexameric +hh +hians +higherperformance +higherprofile +higherseeded +highestresolution +highfin +highprotein +highschools +hijinks +himalayensis +hiragana +hirasei +hirtella +historia +historiated +historicalcritical +historymaking +hitmakers +hitmaking +holdovers +holdup +holospira +homecourt +homemaking +homepages +homer +hominem +homme +homogeneously +hondurensis +hoppy +horsfieldii +horst +horticulturalists +hostesses +hotkeys +houraday +houseboats +housebuilder +huachuca +huegelii +humanrobot +humansized +humantohuman +humanus +humifusa +hundredyear +hybridity +hydrangea +hydrophones +hydroxamic +hydroxyzine +hygroscopicus +hymenophore +hyperopia +hyperplane +hypertelorism +hyperythra +hyphenation +hypnotics +hypolipidemic +iMessage +iN +iPlanet +ibogaine +ic +icestrengthened +icterid +idempotent +identifiably +ideographic +idiophones +idli +idyll +iguanians +ileocolic +illuminance +illwill +imaginal +imbibed +imbibing +immaculatus +immunize +imp +impala +imperialists +impossibly +impressus +impugned +incentivized +inclass +inclusively +inconveniences +incrassata +indazolebased +indicting +indiepunk +indiscretions +indult +indumentum +indurated +industrializing +infarct +infinitedimensional +inflatables +inflationadjusted +infraorders +infringers +ingrown +inhalational +innominata +innominate +inquirybased +inscribe +instanton +interconfederation +internists +interorganizational +interposition +interstitials +intertexta +intragroup +iodinated +iriver +ironbased +ironbinding +irrigating +irritates +isoelectronic +isoquinoline +itThe +italicum +itzingeri +ivories +iwan +jackhammer +jarl +jaunt +jaunty +jetpack +jetski +jewelrymaking +jobbers +jointer +joules +joust +juggles +jujutsu +jumpoff +jumpstarted +juno +juvenilia +kalos +kamacite +kambing +kampong +karoo +kashmir +kati +keepsake +kefir +kenong +keratoderma +kernelmode +ketupat +khyal +kilometres +kittiwake +kivas +kneaded +knocker +knowable +koel +kombucha +kookaburra +kozlovi +kulit +kurodai +kyung +labral +lacera +lacquerware +lacteata +laevipes +lambasted +lambic +lamblia +lamellophone +landscapers +laner +lanolin +lanternshark +lappaceum +lar +latae +latecomer +latemodel +laterals +latifolius +latitudelongitude +latrans +laughingthrushes +lawgiver +laxum +leapfrog +leaseholders +leastknown +lefthanders +leiomyomas +lensshaped +lenta +lepidosaurs +lepospondyls +lepton +lethargic +letterwriting +leucocephalus +leucura +leukoplakia +lexeme +libavcodec +libertine +lichenised +lifeaffirming +lifesavers +lifeways +lignaria +ligne +lignins +liminal +limosus +linamarin +lineare +linearization +lineitem +lingulata +lipoamide +lipolysis +liquidations +liquidfueled +literalism +lithophyte +liveable +loantovalue +lobate +lobelioid +lockets +locution +logicbased +logograms +longclaw +longforgotten +longispinus +longissima +longitudes +longspined +longspur +lorikeets +lovegrass +lowcarbohydrate +lowfare +lowfi +lowmolecularweight +lowpaid +lowvalue +lowveld +ls +lubricate +lubricity +luctuosus +lumens +luminaires +lunacy +lunchroom +lungfishes +lunular +lutung +luxe +luzonicus +mR +mV +macrocycles +macropods +macroptera +macrostoma +macroura +macularis +madras +madrasas +maestros +magdalenae +magicJack +maiming +majorparty +majuscula +makings +malingering +malonate +mandamento +mangos +manholes +mantlet +marinemollusks +maroons +marquisate +marshloving +martyrology +marvels +masa +masculinities +masoni +masqueraded +masseteric +masterplanning +masterslevel +mastheads +matriarchy +mau +mauritanica +maynei +mayri +meadowgrass +mearnsii +meatbased +medicalization +mediumsmall +meetup +megacarpa +megalops +melania +melanism +melanocortin +melanosis +melanoxylon +mellea +memberschool +membersupported +memorability +memoriae +memorymapped +menaces +mentalities +menubased +mercer +mesencephalic +mesoregions +mesothorax +metacarpophalangeal +metaloriented +metamodeling +metapuzzle +metonymic +mgkg +miasma +mich +michaelseni +microcirculation +microcline +microlithiasis +microphthalmus +microprobe +microregions +microscopist +microsimulation +microsomes +microsporidian +microstate +midbody +midflight +midlatitudes +midtwentiethcentury +millefolium +mils +mimula +mindanensis +minifootball +minion +minorkey +misattribution +misfolding +mislabeling +mispricing +misprision +misquoted +missionbased +mitts +mixdown +mixedtraffic +modelmaker +modernizations +modillion +modularized +molted +moneyed +monodon +monogenic +monoplacophorans +monopole +monsoonal +moolavar +morbidly +morganatic +motorgliders +mottes +mountable +mouthing +mps +mr +muchbranched +mucinosis +mucoid +mukims +multiflorum +multigabled +multimachine +multipartisan +multiprogramming +multirotor +multisource +multisyllabic +multitiered +multivesicular +musicvideo +mustachioed +mustcarry +mutilating +mutuals +mynas +myops +myrsinites +mysticete +mz +naivety +nappies +narrowboat +narrowmouth +nasociliary +nat +nationallyrecognized +naumanni +nearcoastal +nearpasserine +necrotrophic +nectridean +nemertean +neoclassicalstyle +neoromantic +neritic +netCDF +nettop +neuro +neurodiversity +neurotensin +newgrass +newlybuilt +newness +newtoni +ngmoco +nicest +nickelplated +nicks +nictitating +nielseni +niente +nightspots +nigrans +nigrofasciatus +nigromaculatus +nigrovittata +nimbus +nineteenyear +nobudget +noctule +nocturnally +nohit +noisemakers +nolonger +nonCommunist +nonFIFA +nonIBM +nonLatin +nonbonding +noncompetition +noncrystalline +nondrafted +nonhomogeneous +nonincumbent +noninternational +nonlawyer +nonmoving +nonneurotoxic +nonnews +nonobvious +nonplanar +nonreactive +nonregistered +nonrelational +nonrival +nonscientists +nonsterile +nonstudent +nontextual +nontraumatic +nonvascular +nonviral +nonword +noseband +nosy +notchback +nothobranch +nourishes +novelised +novicida +nowdeceased +nuclearcapable +nuclearfree +nucleogenic +nulls +nyctosaurid +oathtaking +obliques +oblita +obliterate +obscenities +obscurely +obsoleted +obtusata +occhi +odora +offbalance +offboard +offcolor +offends +officious +offisland +offoffBroadway +offthegrid +offworld +ogee +ogres +oinochoai +okmotu +olsoni +oncedaily +oncepopular +onduty +oneacre +onelevel +onenightonly +onenorth +oneteacher +onground +onhand +onium +ons +onside +ontime +oops +oospores +opals +ophthalmopathy +opsis +optima +opulenta +orbweb +orc +ordains +ordinaria +orebody +oreophila +oreophilus +ornis +oropendola +orthopedist +orthophoto +osculating +osseointegration +osteological +ostomy +otoliths +otorhinolaryngology +outflanked +outgroups +outgrowing +outmaneuver +outmaneuvered +outpointed +outranked +outspokenness +outstations +overburdened +overgrazed +overheat +overpriced +overspending +overstepped +overstretched +oversupply +ovine +oviraptorid +oximetry +oxo +padi +palatability +palates +palatina +palazzi +paleyellow +palmfly +pampered +pandemocracy +pandering +panspermia +papa +papain +pappi +paralogues +parameterization +parapsychological +parasailing +parched +parenthetical +partido +passwordprotected +pathogenassociated +patientspecific +pauperis +paychecks +pe +peacemakers +pearlbordered +pecked +pectineus +pedagogues +peeks +pemphigus +penalizing +penology +pentagona +pentane +pentavalent +pentellations +penthouses +pentoxide +peopletopeople +perceiver +perceivers +perigynium +perilymph +perioral +perplexed +pervert +pes +petiti +pharming +phaselocked +philanthropies +phonetician +phospholipases +phosphoproteins +phosphorescence +phosphoribosyltransferase +photobased +phrenology +phytoremediation +phytosanitary +picea +pigeonholed +pigtails +pileipellis +pining +pinpointed +pipiens +pipped +piqued +piratical +pitman +pitohui +pixelated +pkg +placodonts +plaited +planarity +planteating +plantlets +plastically +platting +platysma +playbook +playercontrolled +plcs +plebejus +pleiotropy +pleurae +plexiglass +plowman +pluvialis +podocytes +poeciliid +pointscore +pointscoring +politicizing +polkadot +pollsters +poloidal +polycarbonates +polyimide +polymerizations +polyvalent +pondered +popolo +poppers +populaire +porcelaneous +porin +porno +porticus +posetal +postbox +postimpressionist +postmarked +postmerger +postpunknew +potto +powerlessness +ppt +practicum +praestans +praomys +preCrisis +precocial +precomputed +predatorprey +predisposing +preelementary +prefatory +preflight +preheated +premillennial +premotor +prepay +prerRNA +prescientific +preservational +preservice +pretax +preussi +preventer +pricklypear +primatial +primigenius +printout +prizegiving +producerrapper +professionalquality +proffer +progestogenic +prohibitionist +proleptic +prolixa +promethium +pronggilled +proofreader +proprietorships +proscriptions +proselytism +prostatespecific +proteinligand +protozoal +provers +psalters +pseudanthium +pseudoacacia +pseudoautosomal +pseudosuchians +psychoacoustic +psychoeducational +psychotherapies +publ +publiclyfunded +pubrestaurant +pulsecode +puniceus +punkhardcore +purism +puritanical +purplegreen +purpletinted +purpuratus +purveys +pushbuttons +pustulosa +putti +pyelogram +pyelonephritis +pyramidshell +pyruvic +qibla +quadband +quadrangularis +quadrat +quadratojugal +quadrifolia +quercifolia +quesadillas +quickrelease +quicksort +quinta +quintile +rabble +racemization +radarguided +radiolabelled +radioman +radiotelephony +radiotracer +ragazza +railbed +rainmaker +ramosus +rancid +randalli +ransacking +ranting +rasping +rattus +rayless +rc +realizable +reaped +rearprojection +reasserting +rebalanced +rebated +rebrands +rebuttals +receiverlinebacker +receptorbinding +recharges +recirculated +reclassifying +reclined +reconfiguring +reconvening +rectilinea +recto +redbordered +redcrowned +redeposited +redpolls +redressed +redshanks +redveined +reeled +reenters +refashioned +referrer +refinanced +regionalization +regulative +rehashed +relaxants +reliving +remediated +remount +renamings +renminbi +renominate +reoccupy +reparative +repertories +repopulate +reprogram +reschedule +rescored +residentialcommercial +resister +resize +resprouting +restive +retable +retouched +retranslated +retroreflector +returners +revelator +revelry +rewinding +rhamnose +rhinovirus +rhomboidal +rhombus +ria +ribeye +rideon +ridgetop +ridiculously +rightside +rigidum +rimu +roadhouses +roadless +roadshows +roars +rockdwelling +rockelectronic +rockface +rockformatted +rollerskating +rollup +ropax +rostered +rostratum +rothi +roughskinned +roughwinged +roundness +routable +roux +rprocess +rubellus +ruffed +rugifrons +runt +rupestre +rushers +rushreleased +russula +rusticana +rustler +rytas +sabbath +sabertooth +sabrewing +saccadic +sacramentary +sacredness +sacrorum +saddest +saddleback +saguaro +salps +saltans +salutatorian +sanctaecrucis +sancti +sandlot +sanitorium +saprobes +saprophyte +sapwood +sara +sarasinorum +sarmentosa +saturate +saundersii +sauropterygian +sauros +savagei +saxicolous +scalariformis +scalpels +scalping +scaphopods +scatterable +schiedea +schneideri +schoolfriend +schottii +scintillators +sclerophylla +screencasting +screwpropelled +scribbled +scrim +scriptura +scuffles +scuppered +seagrasses +seastar +secondbottom +secretagogues +sectorspecific +secundus +seedstage +seeping +selfcriticism +selfdefinition +selfeducation +selfidentifies +selfinterested +selflessness +selfmotivated +selfmutilation +selfowning +selfpity +selfunderstanding +selloff +semihit +semipermanently +semitropical +sensitizer +sententiae +senza +serendipitous +seriocomic +serjeantatlaw +serpens +serverless +servicemember +sessilifolia +seung +sevenbook +sevencard +sevenhour +sevenperson +seventyfirst +seventyninth +sevenwicket +sexbased +sexselective +sexting +sexualised +shakti +shallowest +shapeless +shatters +sheikhs +shelllike +shifty +shikhara +shims +shipmates +shiptoship +shiurim +shive +shola +shortbarreled +shortcrust +shorthold +shoulderwing +shoveler +showjumper +shreds +shri +shrikebabbler +shriveled +shuffleboard +shuttlecraft +siRNAs +sickly +sidecars +sidepassage +sidequests +sidetank +sidetracked +sidevalve +sift +signallers +signalosome +signum +silverback +silverygray +silyl +sind +singerproducer +singlechain +singleonly +singlephoton +singleturreted +sinicus +sinter +sirenians +sitchensis +sitedirected +sitski +sixinch +sixpassenger +skewering +skilfully +skillsbased +skillset +skua +skybox +slanting +slatted +sleeplessness +slights +sloweddown +slowmedium +smacks +smallangle +smallness +smallsize +smartcards +smartweed +smithiana +smokey +smudge +snagged +snagging +snark +sneezeweed +snowfields +snowmen +soapbox +sobering +socioeconomics +sodic +soffit +softdrink +softswitch +softwareopen +soiled +soldi +soleil +soloartist +solomonensis +solubilize +sonorensis +sorghi +soteriology +sourcetosource +southeastnorthwest +southflowing +spaceports +spalling +spanners +speakeasies +speculatively +speleothems +spendthrift +spermatogonia +spheroids +spheromak +spiciness +splake +splitfins +splitlevel +sporocarp +sporozoites +sportsbook +sportsmanlike +spotbilled +spotfin +spritsail +spurts +squabbling +squamatus +squamouscell +squint +stadsdeel +stateofthe +stationkeeping +std +stealer +stepbrothers +stereocenter +stereoselective +stereospondyls +stevedoring +stewarded +stiles +stillbirths +stilted +stingers +stoat +stockists +stonecutters +stonei +stooge +stopovers +storechain +stowing +straightaway +straightchain +straightens +straightsided +stratagem +streambed +streetside +streetsmart +strenua +striatulus +strikeshortened +striven +stroboscopic +stylomastoid +subapicalis +subassembly +subbase +subbasins +subcallosal +subdiscoidal +subflava +subfusca +subgraphs +subiculum +sublists +submaxillary +subnetwork +subnetworks +suborganizations +subsect +subsides +subspecialists +substage +substriatus +subtopics +subverted +sulfatide +sulfonamides +sulfonylureas +suling +sumichrasti +sundae +sunroof +superalloys +supercapacitors +superfeatherweight +superficiality +supergun +superhighway +superintelligent +supermajorities +superpipe +supertribe +surcharged +surfeit +survivin +sustentacular +suteri +swagman +swamy +sward +swarthy +sweetscented +swidden +swirled +swordandsandal +sylvatic +symptomless +synaesthesia +syndicating +synephrine +synthbased +taarab +tabira +tachykinin +tambour +tapaculos +tapebased +taprooted +taxifolia +tearaway +teardropshaped +teaspoon +teats +tejano +telepaths +telerecordings +teleserye +tendonitis +tenman +tenmile +tentorium +tenue +tenuifolium +teosinte +terbium +territoriality +testdriven +testtaker +tetany +tetraplegic +tetrazolium +texanum +thaler +thatching +thenCEO +thenGov +thenState +thenfashionable +thenhead +thenpartner +theorys +therian +thermic +thermochemical +thermogenesis +thermophila +thermophile +thermostable +thermotolerant +thgreatest +thioether +thirdbase +thirddeadliest +thirdparties +thither +thnd +tho +thrd +threadleaf +threeaxis +threecolor +threedecade +threefoot +threehundred +threelane +threeyearlong +thrombolysis +thromboplastin +throwins +thylacine +tiaras +tibetanus +tibetica +tigerfish +tigre +tills +timbering +tinamous +tinkling +titanosauriform +tobacconist +toggling +tomboyish +tomistomine +tooled +toolpaths +topend +tornadogenesis +toro +torpedoboat +torrida +torulosa +toymaker +tr +trackpad +tradecraft +tragicomic +tramtrain +transcatheter +transcendentalist +transcode +transdenominational +transputer +transversefasciata +travail +treeliving +treeshrews +tremens +tremula +trenbolone +tribulation +tridactyla +trifold +trifunctional +trimethylsilyl +trinervia +trinitarian +triviality +trivium +troublemaking +truecolor +trumpetervocalist +trunkfish +ts +tubeless +tufting +tuman +turacos +turbellarian +turboelectric +turningpoint +turnstone +twelvepart +twelvetrack +twotoed +typewritten +udDin +uddin +ufologists +ugliest +ultrawideband +uluguruensis +umbo +umbratica +unIslamic +unarmoured +unchangeable +uncoated +uncoiled +unconquered +unconstitutionality +unconvinced +uncultured +underemployment +underpainting +underpants +undersecretaries +undulated +uneasiness +unexposed +ungainly +unheralded +unicity +unilineata +unimaginable +unindicted +uninformed +uninspiring +unipunctata +unmargined +unmasking +unmethylated +unmolested +unnavigable +unobtrusively +unpacked +unpatriotic +unrealistically +unrepresentative +unsimulated +unspecialized +unspecific +unstudied +untelevised +untreatable +unum +unweighted +upscaling +upstage +upto +urad +urbanrural +uroporphyrinogen +uropygialis +usambarensis +ushers +vachanas +vaginatum +vaginismus +valuer +valveless +vang +vaping +variablepitch +varix +vaunted +ved +velia +velodromes +ventriloquists +vermicularis +vermiculatum +vermiform +verticallyintegrated +verve +vicechairmen +vicecounty +videotelephony +videotex +vietnamensis +villous +viminalis +vincristine +vindicate +violator +viride +viroids +virtuality +visaexempt +viscosum +visionimpaired +visuality +visuallyimpaired +vitriolic +vivant +vocalistrhythm +voix +voluntaryaided +vorax +vous +voxels +wagonway +walkability +walkingsticks +walkouts +wargamers +warmhearted +warthogs +wasabi +wat +watchmakers +waterholes +waterparks +watersaving +waterwheels +waystation +webaccessible +webshow +websteri +weirdest +weirdness +welladapted +wellconserved +wellconstructed +westcoast +westtoeast +wetlease +whammy +wheelbarrows +wherry +whisked +whitebearded +whitespots +whitestem +whitethroats +wickedly +widefield +wierde +windage +windproof +wisest +woe +woodswallows +wordmark +workroom +worldsystems +worldweary +worrisome +wrack +wreckers +wretched +wrinkly +wrongdoer +wrongness +wv +xBase +xcompatible +xeroderma +xerophila +yampah +yaws +yearns +yellowbelly +yellowcolored +yellowwinged +yokes +youthbased +yurt +zale +zap +zeae +zerofare +zincdependent +zipline +zoanthid +zooarchaeology +zoomed +zoot +abbandonata +abbreviates +abruption +absolutist +accentual +accentus +acclaims +acousticbased +acquaint +acreages +acromioclavicular +actionscience +activin +actoractress +actresss +aculeatum +acupressure +adhesins +adipogenesis +adivasi +adjudications +adrenocorticotropic +adunca +advena +aediles +aerate +aerea +aerosolized +aes +affordances +aflame +aftereffect +afterimage +ag +agebased +agegrade +agriculturerelated +agrifood +ahistorical +airlifter +airpark +ajax +alAbbas +alHasakah +alHouthi +alKhatib +alMukhtar +alQura +alae +alberti +albina +albopunctatus +albula +algebraically +alkalic +alkyd +allEnglish +alleghaniensis +allegro +allo +allostatic +alphanumerical +alpicola +alpinism +alterglobalization +altiplano +aluminized +amaniensis +amaurosis +ambassadoratlarge +ameliorating +amici +amicorum +amineassociated +aminoacids +amiodarone +ammoniaoxidizing +amoebic +ampersand +amylopectin +anatolicus +andamanicus +andra +androcentric +androgendependent +angelicus +anguished +anguli +aniconism +animalistic +anis +annatto +annoyances +annunciation +annuus +anomodonts +antedated +antiAmericanism +antiFascist +antiFrench +antiMormon +antiTreaty +antiZionism +antianxiety +antiarmor +anticaking +anticholinergics +anticodon +anticolonialist +anticounterfeiting +antidiarrheal +antifascists +antiinvasion +antimafia +antimigraine +antiparticle +antirevisionist +antiserum +antistate +antitheft +antitorpedo +antitragus +antivaccine +antmimicking +anuran +apalutamide +aphanitic +apo +aposematism +applicationbased +approximans +approximatively +aquanaut +aquaporin +arRahman +arachnoidea +arbiters +arborescence +arbuscula +archangels +archosauromorphs +ardently +arenarium +arenaviruses +areolae +argillite +argo +ariki +arimanni +armlock +aromaticity +arpa +arsons +artemisiae +arthistorical +articulator +artscene +aruensis +asaphid +aspergillosis +assamica +assemblyline +assimilationist +assistantcoach +associational +astarte +asterids +asthenia +astrocytoma +asymptotically +asynchrony +ataxic +atelectasis +athecate +atlantis +atque +atricapilla +attenuates +atypia +auricularis +aurifrons +aurum +austrina +autocannons +autocatalytic +autoerotic +autoinjector +automatics +autorotation +avarice +avocets +axel +azathioprine +azimuthal +bFM +babai +babesiosis +backchannel +backformation +backmasked +backrest +baculovirus +badtempered +bagh +bailing +balk +ballclubs +ballfield +ballista +balsamic +baltica +balticus +bandh +bandsaw +bannerfish +baptistry +barnesi +baroreceptors +barre +bartsia +barycenter +baryon +baryons +baseballonly +baskettail +basse +batandball +battleax +battlement +beatmania +beaufortia +beautified +beeper +beeping +beeps +beetroot +begonia +beijingensis +beisa +bellydance +bematistes +bergeri +berlepschi +bermudensis +bestplaced +bewitching +bharatanatyam +bhutanensis +biaxial +bicornuta +bidirectionally +biennis +billable +bioassays +bioorthogonal +biosensor +bipectinate +biped +biphobia +bipunctalis +bisignata +bistate +bittertasting +blackedged +blackhooded +blackishgray +blacklight +blackmails +blanci +blesses +bloat +blockbased +blondes +bloodbath +bluebird +bluefaced +bluesoriented +boater +boatswain +bobblehead +bodhisattva +bodysuits +bogan +booklice +boonei +boorish +boudoir +bowllike +brachiation +brachydactyly +brainy +brandished +braueri +bravest +breakneck +breams +breastmilk +breviary +brickred +brickyards +bridesmaids +bridetobe +bridgewire +brigadelevel +brightred +brimmed +brindled +briquettes +brisket +briskly +bristlegrass +brittleness +broaching +broadcloth +bronco +bronzecolored +bronzed +brownstones +bruchid +brushwood +buccopharyngeal +buddha +buffetstyle +bufo +bugging +buio +bullnose +bullocks +bumpkin +bung +bungalowstyle +bunghole +bungled +bunkering +burgee +burgeoned +burly +burnings +bushcraft +bushfood +bushmallow +bushwalkers +bushwillow +businessfocused +bustards +busters +butcherbird +buttonweed +cAMPdependent +cDNAs +cabinetmaking +cachet +caco +caespitosum +caffeinefree +calamitous +calicivirus +callouts +calmness +calvary +camming +canaliculatum +cancan +cancerassociated +cantharidin +cantina +capacitation +capense +capindependent +capitalis +capitano +capitols +capybaras +carbamates +carbamoyl +carbocyclic +cardui +careerlong +carfentanil +carillons +carinae +carinipennis +carioca +carmen +carnations +carplet +carpool +cascaded +catechu +catenifer +caul +caulescens +caulk +caved +cavein +cavemen +cavifrons +ceRNA +cebuensis +centrallylocated +centurylong +cepacia +cerastes +cerebelli +ceres +ceroid +cervinus +chachalacas +chaetae +chainring +chainsmoking +chaitya +chalcopyrite +chalkhill +chamaeleon +chansen +chante +chapatis +chathamensis +checkups +cheesemaker +cheilocystidia +chemoattractant +chemoheterotrophic +chestnutcrowned +chevrotain +chickadee +chilis +chimp +chinstrap +chital +chlorantha +chloroformate +choirboys +choker +chokeslam +cholesteryl +choro +christenings +chromaticity +chromatograph +chronobiologist +chuckwalla +chukar +churchgoers +churchwarden +cinematographed +cinnamate +circlet +circovirus +circumdata +cirques +cirrate +citrusflavored +classroombased +claudin +clavi +clavus +cleansers +clewlines +clickthrough +clockmaking +closedcell +cloudhosted +clubfoot +clupeid +coNP +coagulated +coagulopathy +cobwebs +coccineum +cockerel +cockscomb +cocoyam +codesharing +coelomic +coffeeshop +cognizable +coholder +coleading +collagraph +collaterals +collegian +colocalizes +colorado +colorism +colorspace +columellar +combinedcycle +comedymusical +commanderies +commaseparated +commemoratives +commoditized +communicant +communicantes +communityrun +compartmentalization +complexation +comradeship +concocts +concomitantly +concubinage +condoning +confederated +confidants +conformant +conidial +conjugacy +connate +conquistadores +consensually +considerate +consigliere +consolidators +conspersus +constabularies +constrictors +contextbased +contortus +contravening +contrivance +conveniens +conversos +convivial +cooccurrence +cookii +cookingthemed +coolies +cooperators +coorganizing +coppernickel +copulate +coquina +cordiformis +corinthian +cormous +cornets +cornett +corns +coronagraph +corporis +corrugations +corrugatus +corsetry +corvids +cosmetologist +cosmochemistry +costimulatory +coterie +cottongrass +coulteri +councilmembers +countably +counterattacking +counterbattery +countervailing +countryblues +countryformatted +countryinfluenced +couturiers +cowbirds +cownose +coy +cradling +cram +crania +crashlanding +cravat +crawshayi +craziest +creatorexecutive +creditcard +credulity +crematoria +cribripennis +crimecomedydrama +crimesolving +criminalisation +crisper +criteriums +cropmarks +crossbowmen +crossculturally +crossoverfriendly +crossstate +cruciatus +crucibles +cruentus +crypsis +cryptophytes +cryptozoologist +crystallina +ctenophore +cultclassic +cuneifolia +cuniculus +curialis +curlews +cursorial +curvaceous +curvatum +cutandcover +cutandpaste +cutaways +cutback +cyanocephala +cyanogen +cyclecars +cycloalkane +cyclobutane +cyclohydrolase +cyclorama +cyrtochoanitic +dArtois +dAutomobiles +dAuvergne +dEbre +dIberville +dIvry +dabbles +dachshund +dalam +dalmatic +damnatio +danae +dangereuses +danseuse +daphne +dashiki +dastardly +datapath +datas +datasheet +dawsoni +dayi +daymark +db +deadeyes +debenture +debited +debonair +decadal +decadeold +decaryi +decelerating +deciders +declivis +decoction +decolorata +decongest +decry +decurved +decussation +deducting +defers +defile +defoliating +dehalogenase +dehydroepiandrosterone +deinstitutionalization +delamination +delhi +dellEmilia +demerit +demissionary +demodulator +demoralizing +demote +denticulatum +depowered +deprotection +derbid +desa +descant +descarpentriesi +descender +desertus +desi +despoiled +destructively +det +determinacy +deterministically +dethroning +detracting +detrital +deula +devilsbit +devolves +devoutly +dianhydride +diatomaceous +dicotyledon +digenean +diglycerides +digna +dihydroxyacetone +dimensioning +dimerize +dimly +dinerstyle +dingbat +dint +dioicous +diphthongs +diptera +dipteran +directdrive +directionally +directness +dirk +dirtiest +disciplinespecific +diseaserelated +diseasespecific +disenfranchise +disfavored +disheartened +dishonorably +dishonored +disinclined +disincorporate +disinfecting +disown +disparilis +distanti +distillations +distresses +distributorship +districtfree +disturbingly +ditzy +diversities +dockland +docureality +dodecahedra +dogaressa +dognini +dolens +domenica +dominical +dominula +dongs +dosedependent +dosimeters +dotmatrix +doublebanded +doubleender +doublets +doula +downregulate +downshifting +drake +dramahorror +dramaromance +drawbridges +dreamtime +dregeanum +dromedary +drugsmuggling +drumlin +drydocks +dryseason +dualdegree +duathlete +ducalis +dudleyi +duomo +duralumin +durbar +dustpan +dutiful +dyestuffs +dynamiclink +dynamited +dyrosaurid +dysphonia +dysphoric +dyspraxia +dysthymia +eReader +eSTREAM +eServer +earlike +earlytomidth +earworm +eclampsia +ecotype +ecrime +edu +efflorescence +eggfly +egregius +eightdimensional +eightperson +einsteinium +eke +eked +elasmosaurid +electroactive +electrodeposition +electronegative +electronhole +electronicmusic +electrophiles +electroporation +electrum +elevatus +elevenstory +elided +elisabethae +elisae +elision +elixirs +ella +ellagic +elliptocytosis +elongatum +eludes +emodin +emollients +emoryi +emotionality +empirebuilding +employmentbased +emulsifying +encase +encroaches +endocardium +endod +endolymph +endophytes +energizing +enfilade +englynion +engorgement +entertainmentrelated +enticement +entrust +enucleation +environmentfriendly +ependymal +epigenome +epilogs +episodically +epitomizing +equestris +ergative +errans +erythrogaster +essences +ethno +ethnopolitical +eucalypti +eugenicist +eun +euphratica +europium +eutriconodont +everybodys +evidencing +examinee +exboyfriends +excavatum +excavatus +exclaiming +excommunicate +exfiltration +exgirlfriends +exhorts +exocyst +exopeptidase +exoribonuclease +exoskeletal +exoticism +expanders +expartners +expediting +expensively +experiencebased +experimentalists +explicate +explorable +explorative +exslave +exsul +extranet +extraterritoriality +extremophile +facially +fag +fairyfly +fairylike +fait +fallible +familyrelated +familystyle +fanfunded +fanout +fanthroated +fanvoted +farmhands +farmrelated +farnesyltransferase +farrier +fasciated +fasterpaced +fastjet +fathead +fattened +faun +faxed +faxing +fencedoff +fera +fermentative +fernleaf +ferociously +ferruginosa +fervently +festa +ffrench +fibrate +fibrillar +fibrosarcoma +fiends +fifthbusiest +figment +filamentosus +filmgoers +fingered +fireandforget +firebomb +fireprone +fireside +firma +fishbone +fishnet +fishpond +fittingly +fiveseason +fiveyears +fixating +fixity +flails +flammeus +flashbulb +flashcard +flavanonol +flavomaculatus +fleeces +fleshing +flexors +flippant +flotsam +flumazenil +fluoridated +flyaway +flywheels +fnumber +folddown +folia +foodie +foosball +footballplaying +footsoldiers +footstep +foreignbased +forewords +formless +formylation +forticornis +foundereditor +foundling +founds +fourbar +fourbeat +fourbladed +fourfaced +fourhelix +fourhorse +fouronthefloor +fourpanel +foursquare +fourteenstory +fourtoed +fourtower +foxi +frameless +freestyling +freethinker +freetouse +frequentative +freshest +friable +friendtofriend +frivolity +frogman +frontoffice +frosty +frugivore +fruitbody +fruitgrowing +fulgurata +fuliginea +fullseason +fultoni +fulvicornis +fumida +functionalized +funesta +funkacid +funkdisco +furfuracea +fuscicollis +fuscosignatus +gadgetry +gaffe +gaiety +gainoffunction +galagos +galanin +galli +galling +gametogenesis +gametying +gamingrelated +gangway +ganja +gape +garam +garri +gaslift +gasp +gayfeather +gayrights +gazettes +gdb +geayi +generalis +generalship +genetical +genies +genredefying +geodes +geomembranes +geomorphologist +geotagged +gerardi +ghatam +gibbosum +gigolo +glassfiber +glasswork +globemallow +glossa +glyceryl +glycomics +glycopeptides +glycosphingolipids +glyptodont +glyptodonts +gm +gnathos +gnoma +gnosticism +goalsagainst +goatantelope +goatskin +godwits +gofasi +gompa +goniometer +gooseberries +gorgoniantype +gossypinus +gothicstyle +gotomarket +gounellei +governmentimposed +governmentled +governmentwide +grabber +gradelevel +graftversushost +grahami +graminicola +graminifolium +granitoids +grassfed +greenfinch +greengrocer +greenschist +greenveined +grieve +grimy +griseipennis +griseola +groaning +groovetoothed +grooving +groped +grouchy +groundskeepers +growthstage +grungy +guarantors +guesswork +guesthosted +guitaristproducer +gummifera +gunsmithing +guttula +gymnasia +gynoid +gyrate +haciendas +hadrosaurids +haemodialysis +haha +halfinch +halfpage +halfscale +hallmarked +haloalkanes +halve +hammerless +handbells +handeye +handhold +handmaiden +handpowered +handshakes +haploinsufficiency +hardedge +hardliners +harewallaby +harrows +hartwegii +hastatus +hastening +hauntingly +havelis +hawkbit +headfinal +headinitial +headmarking +headstream +healthiest +heartily +heatsink +hedgenettle +heerlijkheid +heinrichi +hemangiomas +hematomas +hematopathology +hemiparasite +hemoglobinuria +hemophiliacs +hempnettle +henrici +hepaticus +hepatobiliary +hercules +herrerae +heterodimerizes +heterodoxa +heteronormativity +heterotopia +hexahydrate +hexahydroxydiphenic +hexandra +hexaploid +hierarch +hierba +highcarbon +highcontrast +highdemand +higherlayer +highflier +highspired +highspirited +hightide +highveld +hilariously +hilarity +hillclimbs +hillslopes +himselfherself +hindlegs +hippodrome +histopathologic +hitchhiked +hitmaker +hm +hnRNP +hoatzin +hocks +hoffmannsi +hollandi +holosericea +hombre +homecountry +homeground +homewardbound +hommes +homopolymers +homothallic +hookah +hookladen +hookshaped +horary +hormonelike +hormonesensitive +hornfels +hornsnail +horological +horrorscience +horsenettle +horsewoman +hosei +hostagetakers +hostpathogen +houseband +hu +hubcaps +huberi +hunchbacked +huntingtin +hussars +huxleyi +hyacinthorchid +hyaluronan +hybridizing +hydel +hydrazone +hydrochlorothiazide +hydrogenases +hydrogenpowered +hygroma +hypercarnivorous +hypergraph +hyperlinking +hypermethylation +hyperpolarization +hypnotize +hypocrites +iAPX +iCN +iChat +iConji +iKON +iShine +iTV +iTaukei +iTree +iboga +iceplant +ichthys +iconographical +ideologues +idiotic +illsuited +illusionistic +imageguided +imatinib +imbricate +immaculately +immanence +imminently +immodest +immunogenetics +immunosuppressants +impish +implementer +implored +implores +imprisons +ina +inbody +incurvata +incurved +indazole +indels +indexation +indexers +indigestible +individuallevel +indoctrinated +indoorarena +industrialism +industryfocused +iners +infernalis +inflammations +infolding +infra +inimitable +injectionmolded +injuryplagued +inlining +innernorthern +inquinata +inrush +inscribing +insecta +insets +insideleft +insignificance +insolens +instructorled +insulare +insularum +insurable +integrum +intellectualism +intensa +interatrial +intercaste +interconvert +interlayer +interlinear +intermediaterange +intermodulation +internationallyknown +internode +interpretable +interrelate +interstices +interstitialis +intertextual +interveniens +intimates +intrahepatic +introducer +inundating +inveterate +invisibly +ionizes +ironical +irrationally +irreparably +irrigates +irritans +isidia +isobaric +isobutyl +isomerases +isopropanol +italica +iterate +iterators +itis +jacana +jacobsi +jag +jailers +jamb +jambs +jansei +jarrah +jazzs +jeepneys +jessamine +jetpropelled +jigsaws +jinx +jogs +jou +jugularis +juju +juliae +junebug +juniperi +junks +kalan +kathak +kaya +kempi +kempul +kennedyi +kenyensis +kephale +kessleri +ketuk +keydependent +keyloggers +kiellandi +kinetically +kinked +kinks +kirkko +kirkyard +klapperichi +klugii +knifemaker +knobbly +knockon +knowledgeintensive +knulli +koehleri +koinobiont +kollari +korean +krai +kraussii +kuwait +kya +kyu +labiatus +labii +lackey +lacrimation +lacunosa +lacus +ladybugs +laetifica +laevipennis +lal +lam +lamin +lamivudine +lamour +landraces +landsale +lang +languagerelated +largeleaved +largeprint +largesse +larrikin +lass +lastnamed +laticauda +latidens +laudable +laudatory +laurisilva +lawbreakers +lawfirm +layabout +layia +leachii +leadbased +leadmining +leaguebest +leaseback +lefty +legalism +legalities +legit +lentiform +lentigines +lentiginosa +leopardus +lepidota +leptosporangiate +lesbianfeminist +lesbianthemed +lessdeveloped +letterboxing +libation +liberationists +librettists +lienholder +ligatus +lightinduced +lightwave +lignans +ligulate +limber +limiters +linebyline +lini +linkup +linocut +liotia +lipomas +liquidfuel +lirae +lithification +litigious +littleleaf +liveliness +loathing +locoweed +lodes +logicians +logotype +loiter +longboarding +longdead +longhand +longispina +longlegs +longstay +lossmaking +loungewear +lousewort +loveteam +lowball +lowborn +lowerleague +lowerquality +lowii +lownoise +lowsulfur +loyally +lube +luciae +ludens +lungwort +lupa +lurch +lusitanicus +lutosa +lycioides +mAb +mach +machineries +macleani +macquariensis +macrocarpus +macroengineering +macronutrients +macrophthalmus +macrosperma +maculicornis +maculifrons +maculiventris +magellanicus +magnirostris +mahal +mahalla +mahout +mainmast +mainspring +majlis +malacologists +malacostracans +malapropisms +malayanus +malayi +malepreference +malleability +malodorous +manca +mancalas +maneaters +manhunts +manicata +manifestly +manmachine +manpowered +manroot +mantidflies +manufacturability +manybranched +maoria +mappa +mara +marigolds +marimbas +markhor +marriageable +martensitic +martian +martinsi +marzban +masochistic +massacring +masterminds +mastodonsauroid +matchsticks +matins +matzo +mazurka +meateating +mechanistically +mechs +medalproducing +mediabased +medicinalis +medlar +medroxyprogesterone +megacolon +megalodon +megalotis +melancholicus +melanoptera +meliloti +melisma +melvilli +memetics +meningioma +mentum +meperidine +merlons +mesylate +metaldeath +metalloproteins +metalsmithing +metanephric +metaplot +metatarsus +metatheory +methanotrophs +methylenedioxy +microbat +microfinancing +microgrids +microliter +microsaurs +middleeastern +midelevation +midfifteenth +midfifties +midmounted +midrash +midthirties +militaryrelated +milks +milliners +mindinabox +minks +minutiflora +mirages +misanthropy +miscredited +misfeasance +misjudged +misl +missouriensis +mistrusted +mittens +mixedgrass +mixin +mjobergi +moaning +mobilis +modestum +moglie +monarchic +monetalis +mongoliensis +monkshood +monnieri +monoamines +monoglycerides +mononymous +monoplacophoran +monopoles +monosomy +monounsaturated +monteithi +moonlights +moorgrass +moralist +morid +morose +morpholine +mosaicist +mostlistenedto +mothball +mothballs +motionsensing +motorman +motorvehicle +motown +mouhoti +mountainbuilding +mountaintops +mouthbrooders +mouthwashes +mpg +msDNA +msnbccom +mt +mufflers +multiPlatinum +multiaxial +multicellularity +multiengined +multifactor +multifasciata +multifield +multiissue +multimodel +multinarrative +multiphonics +multipleuse +multipolar +multipronged +multiregional +multisectoral +multistriata +multitenancy +multiway +muntins +muons +muricatum +muscularis +musiciansongwriter +musing +mutagens +mutates +mutex +mutualisms +muziki +mycoplasma +mycorrhizae +mythologized +nacreous +naively +nama +namechecked +namechecks +nanomachines +naringenin +narratology +narrowboats +natrix +naturallanguage +naturopath +navigability +nearmonopoly +neartotal +nec +necrolysis +neediest +neglectum +negus +nematicide +nematicides +nemo +nemorosa +neoLatin +neocolonial +neoformans +neotype +nephridia +nervously +nettops +networkconnected +networth +neuroethics +neuromodulators +neuromorphic +newbuilt +newsbased +nigeriae +nigeriensis +nigricauda +nigrosignata +nigrovittatus +niobe +nipponica +nitidulus +nitrogens +nocturnus +noirish +noiserock +nomological +nonAnglican +nonAustronesian +nonGovernment +nonItalian +nonNintendo +nonabelian +nonacceptance +nonadjacent +nonbuilding +noncapital +noncircular +noncoalition +noncoercive +noncollecting +nonconducting +noncontroversial +nonedible +nonenzymatic +nonfamily +nonfermentative +nonfluorescent +nongan +nongraded +nonimaging +noninstrumental +nonjusticiable +nonliterary +nonmajor +nonmoral +nonmotil +nonnatives +nonnatural +nonneoplastic +nonoriginal +nonradioactive +nonrelated +nonremovable +nonresistance +nonscholarship +nonsequiturs +nonsubscribers +nontheism +nontherapeutic +nontournament +nori +nosed +nota +notarial +nubicola +nucleate +nudisco +nuke +nullity +nyatiti +oaxacana +objectorientation +objets +obligee +obliterans +obscurior +obviated +occidental +ochrea +octagons +octomaculata +octuple +octyl +odious +odoratus +offbreaks +offcourse +offeror +officialdom +officiant +offnetwork +offroading +offtrail +oilbearing +oiling +oklahomensis +okutanii +oligopeptide +olingo +olla +olympiads +omnes +omnipotence +onagainoffagain +onca +oncethriving +onehundredth +onepass +ontheair +oocysts +opamps +openage +openchain +openend +operatingsystem +opisthoglyphous +opportunists +orangetip +orbitofrontal +orderings +oregana +organochloride +organoleptic +originalism +ornithomimid +orthomolecular +orthoses +osmolarity +osmoregulation +otherworld +otolith +outlasting +outofhospital +outreaches +outstripping +overemphasis +overo +overrepresentation +overripe +overstate +overstating +oversteer +overvalued +overwatch +ovotransferrin +ownerbreeder +oxidization +oxoglutarate +oxygencarrying +oxymoron +pachinko +pacu +paddleshaped +padlocked +padre +pagodula +pai +paidup +paintwork +palatalization +palebellied +palmitoyltransferase +palmyra +panchayaths +pani +pannosa +panoply +pansori +panto +papua +papyrologist +paracompact +paraganglioma +paragon +parallelized +paralympian +parapeted +paraphyly +parapsoriasis +parasympathomimetic +paratope +paraventricular +parboiled +pardalina +pardoning +parentteacher +parka +parterre +partfunded +partialism +parvorder +paso +pasticcio +patel +paucicostata +paupercula +payrolls +paytoplay +peaceably +peakhour +pearlite +peda +pegylated +pelargonium +pelea +pelted +pemphigoid +pendentives +penduliflora +peneplain +penstock +pentachloride +pentapeptide +pentaprism +peopling +pepe +peralkaline +percher +perchlet +percolating +perestroika +perfective +performancerelated +perfunctory +perfused +perilla +perissodactyl +perissodactyls +periventricular +permeation +pernix +persephone +persicum +personifying +pertinax +pertusa +perversa +petticoats +pfefferi +phalloides +pharmacopeia +phasers +phenacetin +phenmetrazine +philippii +philippina +philosophyrelated +phonebook +phonolite +phosphodiesterases +phosphoserine +photoplay +photothermal +phototransduction +phototypesetting +phpBB +phthalates +phycologist +physa +phytopathogenic +phytoplasma +phytoplasmas +pickaxe +pickoff +pictipennis +pictogram +pieve +pigmentary +pigra +pilota +pinewood +pinkpurple +pinnatum +pinout +pipettes +pisiform +pitha +pitlane +pityriasis +pkgsrc +plaits +planation +planatus +planetesimals +planigale +planum +plasmodesmata +playwrightinresidence +plexippus +pliosaurid +plumipes +pocketing +podocarp +poignantly +pointcut +pointspaying +policybased +policys +poling +politicalmilitary +poliziotteschi +pollo +polluter +polyelectrolytes +polygamists +polymerisation +polymorphonuclear +polyolefins +polysemy +polysomnography +pomelo +poncho +pont +ponyfishes +poplin +populates +possessors +postByzantine +postReconstruction +postally +postmatch +postrevolution +postscarcity +posttreatment +potestate +potoroo +pottage +pounce +pourover +powerlifters +pr +praeclara +pragmatist +preSocratic +precalculus +preceramic +precomposed +preconditioning +precovery +predacious +prednisolone +prefigures +prefilled +preheating +preliminarily +premarket +premaxillary +premorbid +preorbital +preposterous +prepreparatory +preprints +presbyters +preselections +presss +presuppositions +prevalently +prickle +primitivism +primordia +princebishop +printrun +printworks +prizewinners +proBrexit +problemist +procerus +processbased +procreate +productiondistribution +progressiveness +progressiverock +pronation +propagandistic +propagator +propagules +propanol +prophage +propranolol +proscribe +prostacyclin +protoporphyrin +prototypic +provinciale +psalmody +pseudomallei +pseudoplatanus +pseudosciences +pseudotuberculosis +psychoanalytical +psychologies +pugilist +pulmonologist +pulsates +pulverea +puncticeps +purposemade +purulent +pushbacks +pustaha +putrescine +putrid +pyogenic +pyrazinamide +pyrazine +quadrilineatus +quadrupleplatinum +quadrupling +quae +qualityadjusted +quarrymen +quartic +quartzites +quasiparticle +quasipeak +quasipublic +quatrefoils +queenless +quicken +quincunx +quinic +quinones +quinquemaculata +quinquestriatus +quip +quipped +quitensis +quoined +rabbet +raceethnicity +racemosum +racerelated +racerunner +racketeers +radials +radiocommunication +radiogram +radiolucent +rafted +ragazzi +rai +railmotors +raincoat +rainfalls +raivaru +rajputs +ramair +rambunctious +rater +ratfish +raymondi +rdeyegirl +reactivating +readying +readytorun +reair +reassigning +reattach +recapped +receival +recognizer +recommence +recompile +reconceptualization +reconditioning +reconsidering +recouping +rectorate +redblack +redder +reddishyellow +reddy +redeploy +rediscovers +redpoll +redundantly +reencryption +reflexology +refound +refractories +refractors +reg +regidor +reginae +regionfree +reheating +reified +reimiro +reinforcer +reiteration +rejoice +rekeying +reliquaries +remarrying +remodels +reoriented +reovirus +repairable +repatriates +reportages +repurchases +requestresponse +reregistration +rerio +respelling +resplendent +restenosis +restock +restorationist +resung +retesting +retouch +retraces +retributive +retroperitoneum +retropharyngeal +rhamnogalacturonan +rheological +rhizoids +rhynchocephalian +riband +ribboncutting +rices +richardsonii +richeri +ricinus +ridable +ridehailing +riderless +rightangles +rightofcenter +ringneck +roachlike +roadies +rockery +rocketassisted +rocketed +rockindie +rockmaster +rockshelter +rogenhoferi +rollingstock +roofer +rotatable +rotundifolium +roughhewn +roundoff +rubberlike +rubbertired +rubbertyred +ruckmen +runcination +ruts +sabermetric +sackbut +sailboard +sailmaker +sallies +salmonis +salsify +saltation +saltmarshes +saltworks +salutary +salve +sanatoriums +sandtreader +sandverbena +sanguinicollis +saron +sartorial +saturatus +satyagraha +satyrs +saussurei +sauteri +savants +sayur +scarcer +scavenges +schlechte +scission +sclerite +sclerophyllous +sclerotic +scones +scotica +scourged +scramblase +screencast +screwpine +scripter +scrobiculata +scroller +scrubbed +sculpturing +seamens +seared +secessionists +secondclosest +secretarymanager +sectionals +secularists +securitys +seeped +segmentalarched +segmentata +seigneur +sejm +selenite +selfacceptance +selfauthorship +selfdetermined +selfeducated +selfhosted +selfing +selflimited +selfperception +selfpollinate +selfpresentation +selfrenewal +selfsacrificing +selfsupported +semaphorin +semaphorins +semialdehyde +semicrystalline +semiflava +semihollow +semiimprovised +semimetal +semiparametric +semisweet +sensationalistic +sentimentalism +serializable +serialize +sericulture +serology +serpyllifolia +serration +serratum +serricornis +sertraline +serval +servitudes +sesquicentenary +setacea +setiger +setpoint +sette +sev +sevenmatch +sevensegment +seventythird +severability +sexton +shackled +shags +shama +shantytown +sharedmemory +sharpei +sharpener +shasum +sheepherders +sheeted +shellcoiling +shepherded +shod +shootemup +shoreside +shortnecked +showtime +showtunes +shreddy +shrieks +shrublike +sibogae +sicca +sideboards +sideloading +sieves +sifaka +sightlines +signee +signups +sikh +silktassel +silvercolored +silversmithing +silverwork +sinfluenced +singalongs +singerpianistsongwriter +singlebyte +singlepage +singleperson +singlesonly +singlewinner +sirolimus +sissy +sistersinlaw +sixdayaweek +sizzling +skinnable +skole +skybridge +skyphoi +skyphos +skyward +slater +slayings +sleuths +slitting +slooprigged +sloughing +sluicing +smallschool +smalltoothed +smelly +snakeeater +snakeeyed +snakeskin +snaking +snappers +sneer +snowfinch +snowtrout +snus +soaks +sobbing +sociolinguists +sociologically +softlaunched +softserve +softshelled +softwinged +softwoods +soi +solemnized +soleus +solium +soloproject +somebodys +somersaults +songbased +songwriterguitarist +songwritingproduction +soothsayer +sophist +soulpop +soundchecks +soupy +sousveillance +souterrain +spaceage +spaceman +speared +speciess +speedcuber +speeded +speedthrash +spellcasting +spenceri +spermalege +spiderorchid +spinulosus +spirulina +spixii +splashdown +splendidula +splenectomy +sponsons +sportfishing +sportrelated +spouting +sprawled +springeri +sprinted +spunky +sputtered +squalida +squama +squamulosa +squaredoff +squashed +squealing +squirrelfish +sri +stackers +stagnate +stares +starflower +stargazer +statemandated +statesmanship +statism +steadfastness +steadicam +stealthily +stearic +steatohepatitis +stepfathers +stereocilia +stereograms +stewing +stigmatica +stirfried +stockmen +stoloniferous +stonecutter +storeandforward +stowaway +straightfour +straightness +stressstrain +striding +stripebacked +studium +stuntwork +stylesheets +styx +subRoman +subareas +subclassification +subcounties +subgeneric +subgrade +subgrouping +sublicensed +sublimate +sublineata +subornata +subprior +subproblems +subsampling +subsiding +subsocial +subspherical +subsumption +subtree +subungual +succinea +sud +sugarbush +suggestibility +sulcicollis +sulfuryl +summeronly +sundries +supercontinents +superdelegate +superheater +superintend +supersoldiers +superteam +supination +suprachiasmatic +suprapubic +supremacism +survivalism +sutra +swallowwort +swamplands +sways +sweetclover +symphonys +syndesmosis +synodical +syntypes +systematization +tPA +tableland +tachycardias +taffeta +taggers +tahr +tailcoat +tailing +taillights +tailpipe +taitensis +talkradio +tanka +tankettes +tanneri +tapeout +taphonomy +tarnishing +tarpon +taser +tasmaniensis +tattered +teambuilding +teardown +teatree +technologydriven +teeing +tegulae +telecare +teleology +teleomorphic +teleoperation +teleosaurid +telerecorded +teletypewriter +televoters +tempest +tencent +tenens +tensioning +tenuiflora +tenuously +teretifolia +terming +termitaria +terrigenous +tesla +tessera +tessmanni +testa +tetrahydrate +tetrahydrofolate +tetraplegia +thcenturies +thenar +thenemerging +thermo +thesauri +thespians +thibetanus +thicketbird +thielei +thiourea +thirdseeded +thirteenpart +threelayered +threerace +threering +throng +throttles +thwarts +thymocytes +thynnid +tickseed +tierra +tigress +timbales +timelessness +timeshifting +timpanist +tinfoil +tinsmith +tintype +toadstool +toileting +tollhouse +tonsured +toolsets +topquality +toprating +toptobottom +torc +torgrass +touchback +touchtone +tourerstyle +townsites +tra +tradeshows +transduce +transducing +transects +transgenesis +transhipment +transitivity +transmigration +transmittance +transposes +treebased +treeplanting +trendsetters +trespasses +triAce +triangulations +triarylmethane +tribological +triceratops +triclosan +trifle +trifluoroacetic +trigeminothalamic +trimaculatus +trimix +trioval +triphenylphosphine +triplestore +triptychs +triquetrum +triune +troika +trp +truant +truer +truest +trumpetfish +trunnion +tryptamines +tryscoring +tubas +tuberculosa +tuckeri +tuffaceous +tumidus +turbidite +turbocharging +turbopump +turnofthethcentury +tuyas +twelveteam +twinspot +twintron +twoacre +twoandonehalf +twocandidatepreferred +twoplatform +tworow +twoseatsintandem +twowinged +twoyears +txt +typhimurium +tyrosinase +tyrosinebased +uDraw +uShaka +ua +udDaulah +ultramarathons +ultrastructural +ultraviolent +umbrata +unaddressed +unassociated +unblock +underachieving +underestimating +underfive +underframes +underslung +underwrites +underwrote +undiminished +undisciplined +undulatum +unexcavated +unexceptional +unfixed +unfrozen +ungrammatical +unguis +unholy +unidentifiable +uninitialized +unipuncta +uniserial +universalis +universalist +unmemorable +unnoticeable +unobservable +unowned +unprincipled +unreached +unrestored +unseasoned +unsegmented +unspent +unsporting +unsprung +unsubstituted +unwinds +uomini +upcurved +upcycling +upfronts +upraised +upshot +upslope +uraeus +urichi +urinated +urokinase +urothelial +userbased +usercontributed +userfriendliness +usufruct +vSphere +vada +valens +vampirelike +vanpool +vanquishing +vara +variablesweep +variola +varyingly +vasospasm +veer +vehiclemounted +vehicleramming +veiling +velata +velopharyngeal +vendorindependent +vento +veritas +versechorus +vespertilio +vestalis +vestrymen +vetches +vexed +vicechairperson +vicechancellors +vicedean +vidhansabha +vihuela +villagelike +vino +violetbacked +virologists +viscida +vitesse +vitrification +vivarium +vividness +vocative +volleys +voltaic +volunteerled +vomeronasal +vorticity +votegetter +voto +vulnerata +wallichiana +wallum +wandoo +waraffected +warhorse +warmtemperate +warungs +washouts +waspfishes +wasplike +wassail +watermarked +waxbill +waxwing +waxworks +waylaid +wd +webenabled +wef +weighbridge +weldability +welfaretowork +wellqualified +wellread +wellspaced +wellstocked +wentletrap +whaleback +wheellock +wherries +whitened +whiteyellow +whiz +wholewheat +widowhood +wie +wil +wilaya +williami +windpump +windrows +wining +wiregrass +withstands +witnesss +woad +wolfpacks +womanowned +womenled +wong +woodcreepers +woodwardi +woollystar +woos +wordlists +workbook +workhorses +workweek +wrenching +wrestlings +wrinkling +writerperformer +writeups +writtendirected +wurtzite +wwwlegislationgovuk +xCD +xanthus +xerographic +xerography +xv +yachtswoman +yakuza +yawing +yea +yellowbacked +yelloweared +yellowishred +yellowjackets +yellowwood +yerburyi +yeti +ylangylang +yolksac +yoni +yourselves +ypsilon +yu +zAAP +zarzuela +zen +zenana +zoek +zymogen +abandonware +abbr +abbrev +abductive +abides +abietina +abjad +abjection +abjuration +abominable +aborting +abri +abut +acaule +accentuates +accreditors +acculturated +acesulfame +acetylacetone +achondrogenesis +achromatopsia +ack +acolytes +acquirers +acquitting +actindependent +actionpuzzle +actormusician +actuate +actuating +acutephase +adamantane +adenoviruses +adidas +adipocyte +adorable +adsorptive +adspersa +advocation +aegirine +aerobically +aerophone +aerophones +africa +agammaglobulinemia +agender +agranulocytosis +agreeableness +agricultures +agua +aimlessly +airdried +airfares +airfreight +akathisia +alAnsar +alJihad +alMuayyad +alMulk +alQassam +alQuwain +alRashid +alShaykh +alShibh +alTabari +alatus +albicincta +albimacula +albiplaga +alboguttata +albolateralis +albopicta +albopictus +albumDVD +alecto +allRussian +allopathic +allopurinol +allozymes +allresidential +allroad +allseason +allstock +allusive +alongwith +alphasialyltransferase +altimetry +alunalun +ambiguum +amd +ameliae +ameliorated +amieti +aminoacylation +amitriptyline +amniocentesis +ampakine +amphibolite +analogdigital +anaphoric +anarchocapitalists +andalusite +androecium +androgyny +andromeda +ane +anemias +anemic +angasi +angeli +angiogenic +angiopathy +anglica +anguid +angulated +angulation +angustior +angustissima +angwantibo +anhedral +annulate +anomeric +ansorgii +answerer +antarctic +antarcticum +anteroventral +anthracene +anthropoid +anthropometry +anthroposophist +anthroposophy +antiStalinist +antibodymediated +anticlericalism +anticlinal +anticommercial +antifeminism +antihelix +antiincumbency +antiinsurgency +antimitotic +antimutant +antimycobacterial +antinociceptive +antioxidative +antiport +antiquum +antireflective +antiroll +aolid +aperitif +apertus +apiary +apiculture +applicationoriented +appraising +approximatus +apterus +aquathlon +aquatint +arabesques +arbitrates +arborist +archaean +archaebacteria +archduke +architectonic +archosaurian +arenaindoor +argid +arietinum +armatum +armeniacus +armorer +arrhythmogenic +arrowheadshaped +arrowleaf +artbased +arte +artforms +arthropathy +arthropodborne +arthuri +artificer +arty +asbestosis +ashen +ashmaple +asian +asteroides +astrocytomas +astrodynamics +atheneum +atomism +atrophied +attainders +attenuatum +attributive +aubergine +auctioneering +aupouria +aurescens +auricoma +auriculatus +auripennis +aurorae +austeni +austerities +australasica +authoreditor +autoconfiguration +autogenic +automobilerelated +automorphisms +autopilots +autotomy +autotransporter +ave +avenae +avenges +azapirone +azithromycin +babysitters +bacilliform +backdoors +backroads +baclofen +bactericide +bagasse +baguette +bahamensis +bailli +baja +balancers +baller +ballhandling +barbieri +barbthroat +barfi +barodontalgia +baronium +barramundi +bartered +barteri +basepair +bashed +basicity +basidium +bask +bassdriven +bassguitar +bastarda +bastide +bayan +bayberry +beachcombers +beaching +beanie +beatem +beckii +bedpan +beguinage +behaviorists +belids +bellflowers +bellhop +bemused +bendahara +benefactress +beneficent +beneficiarys +bennettii +bentwing +benzimidazole +benzofuran +benzophenone +benzoquinone +berated +bergii +beseech +bestcharting +besteffort +betaDfructan +betaoxidation +bg +bhavas +biarticulated +bibliophiles +bibroni +bicincta +bicker +bicolora +biconica +bicostata +biennium +bigamist +bilaminar +bindery +biobehavioral +biocatalysis +biochar +biocultural +biodegradability +bioplastics +biorefinery +biotechnologies +bipinnata +biplagiatus +bipustulata +biretta +bitesize +bitterling +bituberculata +blackbutt +blackdeath +blackthrash +blackveined +blakei +blameless +blanfordi +blastomycosis +blatchleyi +blaue +blazar +bleheri +blesseds +bleue +blip +blissfully +blistered +bloods +bloomed +blu +blueandwhite +bluebonnet +bluebreasted +bluelight +bluetongue +bluishblack +blunttipped +boatbuilders +bodystyles +bol +bolivari +bombmaker +bonafide +bonefish +bookies +bookkeepers +bookplates +borbonica +borndigital +borneana +bosuns +bottlenecked +bowmen +brachyphylla +brachypterous +bracteatus +bracted +bradleyi +brags +brauni +braved +breakdancer +breastbone +breasted +brevispinus +brickmaker +brigadiers +brimming +bristletipped +brit +brittlegills +broadcastquality +bromo +brooded +broodmares +broomstick +brotula +brownfields +brownwater +brunnealis +brunneipennis +brushlegged +brushturkey +brusque +bu +bubbler +bubur +budworm +buffbellied +buffbreasted +buffish +bugged +bulbifera +bulletshaped +bullettes +bullfights +burh +burmeisteri +burntout +bursage +buruensis +bushranging +businessmans +buttercups +butterfish +butterflys +butyrophenone +buyside +byggnadsminne +cJun +cadastre +cadaveric +cagebird +calcar +calcined +calfskin +calibrations +calla +calmed +calumnia +calvaria +camaldulensis +camerae +cameroni +camptown +canariense +canarygrass +candicans +canephora +canna +canso +cantabile +canticle +capandtrade +capbinding +capitalizationweighted +capitation +capitularies +caproate +capsularis +capsulatus +captaincoaching +capuchins +caracal +carbonarius +cardiotoxic +cardsized +cargocarrying +caridean +cartesian +cartographical +caruncle +carvel +caryae +casebearer +casei +cashstrapped +cassowaries +castrati +casuarina +catadromous +catafalque +cataloguer +catechist +catecholaminergic +cath +catnip +catuaba +caucasicus +cavallo +cavernicola +cayennensis +ceftriaxone +celatus +celebica +cellulosebased +cellwall +cena +centimes +centimeterlong +centra +centre +centreline +centroids +centrosaurines +cerata +ceratina +cercle +cereale +cerebellopontine +certainties +cest +chalking +challengeresponse +cham +chancellorship +chank +chapaensis +chapati +chapelries +chaplaincies +chaps +chard +charmers +charring +chartarum +charterhouse +chartreuse +chateaux +checkbox +checkpointing +checkweighman +cheesmanae +chefowner +cheilitis +chemoreceptor +chemosynthesis +cheni +chestnutheaded +chia +chica +chicklit +chihuahua +childrenoriented +chiles +chinese +chirp +chlamydospores +chlordiazepoxide +cholangiocarcinoma +cholesterollowering +chopstick +chorded +chording +chordophones +choreographs +christen +chromeplated +chromeyellow +chronozone +churchwardens +churchwide +chyme +ciliogenesis +cincinnata +cinctella +cinctipes +cinematheque +cineplex +cinereiceps +cinnabarinus +circumcincta +circumnavigates +cislunar +citable +citril +civilianmilitary +cladodes +clansmen +clarifications +clasper +classicallytrained +classis +clathrincoated +clayrich +cleancut +cleanshaven +cleavers +cleistogamy +clenching +cliffhangers +clinicalstage +clitic +clonidine +closedoff +clotrimazole +cloture +cloudforests +cloudiness +clubdance +clubrush +clusterlily +coCEOs +coachbuilt +coarctation +coccusshaped +coccygeal +cocoach +coconsul +coconvenor +codefensive +codexes +codistributed +codling +coelenterates +coenzymes +coerces +coercing +coercivity +coeur +coevolutionary +coexpression +coffer +cogent +cohabit +coheadliners +cojoined +colas +colchica +coldpressed +coldrain +coleoids +collaged +collard +collectivist +collectivized +colliculi +collimation +colloquialisms +collude +collybioides +coloboma +coltan +colubrids +comanagement +combinatory +commandandcontrol +commend +commentate +commingled +comminution +communitylevel +commutations +complementarian +compositors +composti +compressedair +comprimario +compulsions +comuna +conceptualism +concessionary +concordia +concretion +conditionals +condors +confertus +configurability +configures +conflate +conglutinate +congolensis +connectionism +connectives +conservativeleaning +consignor +consolidator +conspicillatus +constrictive +containerships +contemporaryformatted +contextualization +continentwide +continuouslyrunning +contractionary +contrada +contrapposto +converses +convoyed +convulsive +coorbital +copilots +coppersmith +copyhold +copywriters +coquette +coram +corax +cordovan +corella +coriaceous +coronial +corporatised +correctors +corrodes +coruled +corulers +corymbs +cose +cosponsorship +costatum +costed +costeffectively +cosubstrate +cotoneaster +cots +countermanded +countermelody +counterprotest +counterprotesters +counterweights +countout +countybased +countyequivalent +courtmartialled +cowherd +cr +craftbased +crameri +crassiceps +crataegi +cratonic +craved +creatorproducer +crenulatus +crewmates +crimerelated +crippleware +crispness +cristae +critter +croceus +crooned +crosscheck +crosscontamination +crosscounty +crosscuts +crosseyed +crosslanguage +crosswinds +crouch +croup +croupier +crowdsources +crowngroup +cruentata +cryoelectron +cryptograms +cryptologist +cryptomonads +cryptovirology +crystalclear +crystallinity +cuddle +cuddling +cufflinks +cuing +culturalhistorical +culturespecific +cupronickel +curiosa +curvebilled +customerpremises +customizability +cuticles +cutthrough +cwmwd +cyanus +cyclases +cyclopropene +cyclosporin +cynically +cyrtocone +cysteinyl +cytoarchitecture +cytolytic +dAlger +dAlsace +dAnnunzio +dEtudes +dOrbigny +dUrbervilles +dactylifera +dagga +dandelions +dapple +dapsone +databased +dataprocessing +datasheets +dauber +davits +dayandnight +daybook +deadbeat +deathmatches +debile +debtridden +decapeptide +decapping +decemvirate +deceptrix +decidual +decimals +declarant +decompressor +deconstructionist +decussatus +defeasible +definitional +degradative +dehumidifiers +dehydrating +deidentification +deildegast +delavayi +deliciosus +delimits +dellAdriatico +dellOpera +delphinidin +deltav +demarzi +demissa +demodulate +demonetized +demoralize +demotic +deniable +densification +depauperata +depletes +depolymerizing +deprivations +deprotonated +depsipeptide +dept +derailments +deregister +dermatophyte +descadre +desertlike +deservedly +designees +desorptionionization +despondency +desulfurization +determinable +detracts +deutschen +developerpublisher +devilish +dham +dholak +dia +diagnostically +dialectologist +diarchy +diatribe +diborane +dichotomus +diclofenac +didactics +didst +dieoffs +differencing +digitalization +digitigrade +digraph +dihydrocodeine +dihydroisocoumarin +dihydromorphine +dilatatum +diminutives +diomedea +diopetes +diplococci +diptychs +disaggregated +disassembler +discoinfluenced +disconnector +discosorid +discosorids +diseaseresistant +disengaging +disillusion +disinfected +disjunctus +dislocating +dismantles +dispersions +displaystyle +displeasing +disproportionality +disses +dissimilarity +districting +disubstituted +diviners +divisioona +dizzy +djs +dm +dockets +dodecahedral +doeuvre +doggerel +dogmatics +dolce +domelike +dominicana +dominus +dorado +dorcas +doric +dorsata +doublebarrelled +doubleclick +doublecurvature +doublepen +doubler +doubletracking +downregulates +downstroke +draftsmanship +dragnet +dramaseries +draperies +draughting +drawl +dressmakers +droughtresistant +drubbing +drugdealer +drugdealing +drugging +druglike +dualities +duallanguage +dualmember +dualspecificity +dualvoltage +duchesses +dumbwaiter +dumetorum +duncani +dunked +dunkeri +duogroup +durables +duskywing +dvips +dwarven +dwm +dynactin +dysgalactiae +dystonic +dysuria +eCos +eHarmony +eXtreme +ealdorman +earlyBaroque +earlyRenaissance +earmark +earmarking +earring +eastend +ecclesiological +eclectus +ecofeminist +ecologic +ectoparasitoids +ecuadoriensis +eczematous +edematous +edtech +eelpouts +effusa +efile +egotism +egressive +ehealth +ei +eider +eightacre +eightcounty +eighteenhole +eighths +eightpoint +eightyfourth +eightyseventh +ekmanii +elation +elatior +electroRB +electrophile +electrophysiologist +electrotherapy +eleventrack +elgonensis +elliptically +elmeri +elucidates +eluvial +emailbased +emancipatory +embanked +embeddedness +embezzler +embolic +embroiderer +embryologic +embryonal +emertoni +employments +empresses +enV +enceinte +encephalomyopathy +enchantments +enchiladas +endochondral +endodeoxyribonuclease +endometritis +endosome +energyefficiency +enfeoffed +engrossing +engulfs +enigmas +enjoining +enkephalin +enquired +enshrinement +enterocytes +enteroviruses +enterprisegrade +entertainmentbased +enthralling +enthused +entices +entreaties +entrenches +entrustment +entryways +enumerations +enunciate +enunciation +enuresis +env +enzymecatalyzed +epigyne +epilepsies +epilepticus +epiphysis +epipubic +episiotomy +epochal +epoxygenases +eprocurement +equalarea +equalities +equitans +erbium +eremic +eremicola +eremitic +ergaster +ergometer +erinaceum +erlangeri +erotically +erraticus +erythroblasts +escapology +escheat +essaying +essentialist +ethoxylation +eubacterial +eulogies +eustatic +evades +ewer +exBlack +exMuslim +exboxer +exemplification +exfoliating +exhaling +exhibitionism +exigency +exminister +exmodel +exocyclic +exogenously +exoplanetary +expiratory +explicable +expolice +extendedrelease +extraplanar +extrapolating +extrema +exuviae +fRoots +faafafine +fabricii +facilis +factice +factotum +fairywrens +falafel +falcataria +falcatum +familiars +fanfriendly +fang +fangame +fanless +fanlights +fantastically +farebox +farranging +fascicular +fasciculosa +fasciolatus +fastidiosa +fatalism +fatherly +fattening +fatter +fatwas +fe +federallyrecognized +felina +feminized +ferryman +ferrys +fervidus +festivus +fetishist +fiberbased +fibrinolytic +fibrocartilaginous +fibrocystic +fibromas +fibromatosis +fictionpolice +fieldstones +fim +fimbriated +fingerholes +finschi +fireadapted +firebase +fireboxes +firecolored +firetail +firstplaced +fitters +fiveact +fiveeighths +fivenight +fivereel +fiveseater +fixedincome +fixedwidth +fka +flabby +flamed +flatfour +flatroof +flavoproteins +flavovirens +flavovittatus +fleck +fledglings +fleur +flicked +floaters +flocculent +floes +floodgate +florae +floristry +floundering +flowerrich +flues +fluidly +fluorosis +flyback +flypast +fogging +folklores +fondue +foodplants +foodprocessing +foolishness +footwide +forepart +foresail +forestalled +foretells +forgetful +forgoes +formbased +forrestii +forza +fosbergii +fossicking +fosteri +fou +foursomes +fourstep +fourthhighestgrossing +foxhole +frameup +franchisebased +francophones +freakish +freeforall +freereed +freeskiing +freest +freezedried +frenchi +fricative +friday +frizzen +frolic +fructus +fruiteater +fucata +fuciformis +fucked +fugacity +fullbore +fulldepth +fullmarket +fullrange +fullstack +fumata +fumetti +funereal +funfilled +fungicidal +funkjazz +furca +furcifer +furlined +furred +gadogado +gadwalls +gahani +galangal +galba +gallinae +gallotannins +gamertag +gamigo +gangeticus +ganglioside +gardenia +gargantuan +gasgenerator +gash +gatra +gazella +gelisols +gellike +geminus +genei +genericised +genitalium +genotypic +geomechanics +geosphere +germanus +gesso +gettogether +ghanensis +ghostlike +ghoul +ghoulish +gibberellin +gigawatts +gilva +gingivalis +girardi +giro +gist +glabricollis +glace +glasshouses +glassmakers +glazier +glaziovii +glean +glitz +glossystarling +glovebox +glycines +glycoalkaloid +gneisses +gobiensis +gobo +goldbased +goldbearing +golgi +goodfaith +gooey +gordonii +gores +gouges +governmentsubsidized +governorelect +gracilicornis +grandly +grandslam +grangeri +granitoid +granzymes +grapegrowing +grapeshot +graphed +grappled +grassbird +grates +gravitating +grayblack +grayblue +graycolored +graying +greaser +greatgreatgreat +grebo +greenbacks +greenii +greenishblue +greenlet +greenling +greenskinned +greentailed +greenyellow +greylag +greywacke +grindstones +groenlandica +gromwell +groundfeeding +grudgingly +guanylyl +guestedited +guesthouses +guillotined +guineense +gular +gullwing +gumballs +guppies +gusto +gutsy +gyroelongating +gyros +haLevi +haat +habenular +hackneyed +hadrosauroid +hairthin +halfdemon +halfman +halfround +halfstaff +halfstep +halloween +haloalkane +halteres +hammerbeam +hampers +handcrafting +handlettered +handloading +handlooms +handout +handrolled +handwired +hangingfly +hangouts +haptoglobin +harddrinking +hardtack +hardtail +hardwickii +hardyi +harterti +hasta +hatchlings +hating +haughty +haulers +havanensis +headbutt +headbutting +headsails +headshot +headshunt +heathens +heathi +heatwave +hebes +hebridarum +hecate +hedged +heifers +helds +hematophagous +hendersoni +hentzi +hepatotoxic +hepcidin +herbicidal +hereinafter +heritors +hermanni +hesperia +heterochrony +heteroclinic +heterostructure +heterostructures +heterotetramer +heterotroph +heterozygotes +heydeni +hg +highalbedo +highcalorie +highcost +highcurrent +highlanders +highmounted +highrate +highreliability +highsensitivity +highsociety +highyielding +hihats +hildebrandtii +hilltopping +hilts +hiphoprap +histidines +histiocytes +histiocytoma +histopathological +hitched +hitsingle +hl +hnRNPs +hoarseness +hob +hogshead +hogweed +hollies +holometabolous +holomorphic +homomorphisms +homopolar +hon +honeycreepers +honeypots +hookers +hopRB +hormonereleasing +hornbeams +hornii +hornshaped +hornworm +horrormysteryerotica +horsemounted +hospitalist +hothouse +hotswappable +hotwater +hoverboard +howls +hulking +humanbased +humanize +humanizing +hummock +hun +hus +husbandmen +huur +hyaloclastite +hybridelectric +hybridoma +hydatid +hydrocodone +hydroformylation +hydrolyzable +hyperboreus +hypergiant +hypergraphs +hypersexuality +hypertonia +hypochondriac +hypocotyl +hypogeal +hypogean +hypogeum +hypostatic +hypotonic +iF +iNews +icefall +ices +icicle +iconoclasts +ictal +idaeus +ideograms +idleness +ignita +iguanid +iiNet +ikat +ileocecal +iliopsoas +iliotibial +illudens +illusionists +imagistic +imbibition +immunologists +impetuous +implacable +impositions +impregnating +imprisonments +inattention +incardinated +incisus +incomegenerating +inconspicuus +inconstant +incorporations +incorruptible +indents +indigene +indigetes +indistinctly +indistinctus +indoles +indomalayan +inextricable +infirmaries +infix +inflatum +influenzalike +infrahyoid +infrasonic +infrasound +infraspinatus +infructescence +ingratiation +ingressive +inground +inheritors +inholding +injectables +inkblot +inkstand +innameonly +innervating +innotata +innumeracy +inorbit +inordinately +inquisitors +inroom +inscripta +inscriptional +insolubility +instabilis +instants +instillation +instream +instructionlevel +instrumentalonly +insufflation +insulins +interapplication +interceded +intercolonial +intercommunication +intercoms +intercondylar +intercropping +interdicted +interdictor +intergiro +intergrade +intermediatemass +internationalize +internee +interossei +interpolating +intersymbol +interurbans +intestacy +intracardiac +intraspecies +intrathoracic +intuitionistic +invalidly +inviolable +invisa +iodides +ionophores +irinotecan +irish +irrefutable +irrelevance +irretrievably +isabellina +isentropic +isis +isoflurane +isogonal +isomerisation +isotretinoin +isthmica +ixioides +jacksonii +jaegeri +jailbreaks +jangling +javanicum +jawline +jazzblues +jeg +jejuensis +jeotgal +jeune +jewish +jitsu +jobrelated +jobseekers +johannis +johns +jojoba +josephinae +jovian +joyriding +jubilees +judicatory +judice +juiced +jul +jurisprudential +jut +juxta +juxtapose +kanjira +kaolinite +kapok +karatedo +karschi +karsholti +kaval +kcal +keralensis +keratan +keratectomy +keratoses +keratotic +kerk +kethuk +ketoprak +keybindings +keyensis +keyserlingi +kickboxers +kilim +killdeer +kilobits +kimberleyensis +kina +kinetoplast +kingi +kingmaker +kirki +klossi +knapping +knickerbockers +knickpoint +knifefishes +knotty +kotschyi +kraits +krishna +kulintang +kumquat +kund +kundalini +kunya +kynurenine +lAvenir +lOntario +labyrinthodonts +lacelike +lacrosseonly +lactiferous +lahar +lamarckii +lambdoid +lamberti +lamellata +lamia +laminectomy +landfalls +languagespeaking +languida +lappet +larches +largecell +largeleaf +largestcirculation +largetoothed +largish +larseni +laserassisted +lasso +latefasciatus +latenineteenthcentury +lateralization +laters +latirostris +latum +lawrencei +layardi +lbf +leadsingle +leafflower +leafroll +leai +leastused +leftism +leftside +legge +leggenda +legrandi +lekking +lemoulti +lengua +lentiginous +leonardi +leonis +leptons +lespedeza +lesscommon +lessthantruckload +leuconota +levitated +liaised +liberator +libpcap +libro +licentiousness +lichenoides +liege +lifelines +lifesustaining +lightbrown +lightdependent +lightyear +lijsttrekker +likelier +limata +linda +lineament +linemandefensive +lingers +linksstyle +linnet +lipidomics +lipreading +lirica +liui +liveactionCGI +liveactioncomputeranimated +liveinthestudio +livemusic +lividum +locallyproduced +localregional +locationspecific +lockkeepers +lockon +lockouts +locksmiths +loggerheads +loi +lolita +lon +loners +longbox +longclaws +longerestablished +longfooted +longisporum +longplayer +longshort +longsnout +longspine +loosed +loris +lostwax +loveinterest +loveridgei +lovestruck +lowbypass +lowermiddleclass +lowestlevel +lowestranking +lowlife +lowpaying +lowwinged +loxP +lumawigi +lumbermen +lumborum +lupines +lupulus +lustful +luxuriously +lyallii +lydekkerianus +lymphadenitis +lyssavirus +machismo +macrocosm +macroeconometric +macrolides +macrospora +madagascariense +maderensis +madia +mafiatype +magmatism +magnesiumrich +magnetohydrodynamic +magnetooptical +magnetostrictive +magnoliids +maguey +maia +maim +mainwheels +maisonette +majordomo +malagasy +mallows +malonic +mammoplasia +mammy +manat +mand +manhandled +manicure +mantidfly +manu +manumitted +manycolored +mapbased +mapboards +mapmakers +maquette +marchio +margaritacea +margraviate +mariculture +marketrate +markups +marmalades +martialarts +martialed +masers +masseuse +masterclass +masticatory +mataiapo +matchboxes +matchings +matricellular +matrixassociated +matthewsii +maven +meanspirited +meatless +mechanician +mechanotransduction +mediations +medico +medio +mediumformat +mediumlength +mediumscale +mediumweight +medullaris +megalitres +megastar +megastructure +melanistic +melanopus +melanosticta +melomys +memetic +mendicants +menstruating +meow +meprobamate +merchandised +meri +meridensis +meridianus +meridional +merlin +merous +merrymaking +merula +meshless +mesmerized +mesocortex +mesophytic +mesothelium +metacognition +metalinfluenced +metalliferous +metalloids +metalthrash +metalware +metaphilosophy +meteoroid +methylenetetrahydrofolate +methylmalonic +metriorhynchid +metrosexual +mettle +micelle +michaeli +micranthum +microalga +microbats +microblogs +microburst +microcap +microdissection +microdon +microevolution +microfilming +microgames +microlights +micromarketing +microphthalma +microplates +microstoma +midCretaceous +middecade +middistance +midlatitude +midpaced +midrd +midtolates +mileposts +milfoil +millenniums +mimes +mimesis +mineralisation +minggah +miniatus +minidocumentary +miniflyweight +minihumbucker +minisite +minusculus +minutissimum +minyan +misapplication +misheard +misinformed +misperceptions +mispronunciation +mistreating +miterwort +mitomycin +mitred +mixedNOCs +mixedvoice +mixins +mocha +modernizes +modernly +modiglianii +modiolus +modo +molder +molesta +mollymawk +moluccana +momenta +monads +moneda +monetarily +monocrystalline +monodi +monohulls +monolinguals +mononeuropathy +monopolist +monotony +monotypical +monozygotic +monthslong +moog +moonlit +moonseed +moots +mopping +morphometrics +morrisii +mortared +mossi +mosslike +mostadded +mostknown +mostperformed +motmots +mountainaccess +mourns +mousy +mouthfeel +movierelated +muchanticipated +mucida +mucins +mucolytic +muerte +muffed +muffled +mulching +mullions +multiangle +multiasset +multibody +multiclass +multicostata +multigame +multigene +multihop +multilineata +multimammate +multimeter +multiobjective +multiplayeronly +multipleinput +multiplications +multipole +multiround +multisporting +multistrategy +multocida +mummers +mura +muralists +murdermystery +mus +muscled +museologist +mushers +mushroomtongue +mushy +mustangs +musthave +mutabile +mutator +mutilate +mutuality +myersi +mysids +mysterycrime +mysterydrama +mysterysuspense +myxozoan +nVidia +naiads +naivete +nameservers +nannodes +nanobiotechnology +nanocomposites +nanofabrication +nanophotonics +nanoscopic +nanosponges +napus +narrowheaded +nasals +nationalinternational +nationalsecurity +natura +naturopathy +navigations +ndlargest +ndu +nearrecord +nebulosity +necromancy +nectars +needling +neophyte +neotenic +neotropica +nephrosis +nereis +neriifolia +nesiotes +netlabels +neuroendocrinology +neurokinin +neuroleptics +neuropathologist +neuropil +neuroprotection +neuroradiology +neurostimulation +nevermanni +newlyelected +newsboy +newsfeed +newsfeeds +ngoma +nicefori +nif +nigella +nightflying +nightingalethrush +nightwatchman +nigripuncta +ninebark +nineepisode +ninemile +ninetieth +ninetythree +nipa +nitration +nitre +nitromethane +nobiliary +nociceptin +noire +nomadism +nominalism +nonAboriginal +nonIslamic +nonalignment +nonanimated +nonbasic +nonbiodegradable +nonblack +noncelebrity +nonchalant +noncognitivism +noncollagenous +noncommercially +noncompilation +nondrafting +nonelectrical +nonextant +nonfigurative +nongeographical +nonhistone +nonhybrid +nonleap +nonlifethreatening +nonlocality +nonnetwork +nonneuronal +nonnormative +nonobviousness +nonpersistent +nonproportional +nonreflective +nonrepudiation +nonrestricted +nonsanctioned +nonscripta +nonserious +nonsingular +nontidal +nontimber +norepinephrinedopamine +normalizes +normani +normativity +norovirus +nortestosterone +noster +nostra +notehead +notepad +nothofagi +noticeboard +novellength +nowcanceled +nowretired +npower +nucleotidegated +nudists +nul +numerator +nummularia +nymphaeum +oC +oast +obfuscate +objectify +objectivebased +oblates +obliterata +obliterating +obovatus +obviousness +occipitotemporal +occlude +occlusive +occulting +occultists +ochraceum +octogenarian +officebearer +offputting +offthecuff +oftenused +ohmic +oilfilled +ok +oldiesclassic +oleaster +omental +omissa +omnitruncated +omphalinoid +onager +onehorse +oneliner +onestring +onfarm +openangle +openwheeled +ophioglossoides +ophiolites +ophthalmoplegia +opilio +opimus +opposable +opposedpiston +optimates +orangespotted +orbis +oregona +organisationally +organismic +organizationwide +organoids +oribatid +orientational +ornatipes +ornatum +orographically +orthometric +osteopaths +otaku +otome +outlawry +outoffocus +outpolled +outrages +outrighted +outsmart +outspread +outwits +ovalifolium +ovenbirds +overblowing +overcurrent +overfished +overfitting +overflight +overlong +oversimplification +overtopping +ovipennis +oviraptorosaurs +ovulate +owlflies +owlsclover +oxaloacetate +oxoacid +oxyanion +oxygenic +oyer +pachycephalosaurs +pacifying +paddlefish +paella +pagana +painlessly +palaeographer +palaestra +palay +paleobotanist +paleography +palla +pallasite +palliatus +palma +palpal +panIndia +panchromatic +pandora +paned +pangenome +panhandling +paniculate +panniculus +panniers +papilledema +paraaortic +paraboloid +paracanoe +paralimbic +parasitised +parastatals +paratonia +paratroops +parchmentlike +parfait +parietooccipital +parliamentarism +parol +particleboard +partita +partwork +parviflorus +parvifolium +paspalum +passagerequired +pata +paternoster +patriarchates +patristics +patronal +pattress +pau +paume +pawl +pawnbrokers +paygrade +peacockpheasant +pearcei +pebbledash +pectinase +peculiaris +pedata +pedestrianfriendly +pedestrianization +pedestrianonly +pedipalp +peeping +peeress +pegasus +pellagra +pelle +pellitory +pemoline +penciltailed +penduline +penetrations +penpal +pensioned +pensylvanicus +pentamer +peonage +percepts +percolate +perenne +performingarts +perfumers +pergandei +periaqueductal +periderm +perimenopause +perineurium +perm +permadeath +permeating +personalizing +perspex +perturb +pervasiveness +perverted +peseta +petersii +petrophila +peyrierasi +phenethyl +phenylpropene +philippiana +philippinus +philtrum +phlebotomy +phloroglucinol +phoenicea +phonebased +phono +phosphatidylethanolamine +phosphocreatine +photoacoustic +photogrammetric +photoionization +photoperiod +phycology +physiochemical +phytoalexin +phytogeography +pianistbandleader +pici +picturatus +picturized +piedra +piercers +pietas +pietra +piger +piha +pileata +pilosella +pinelands +pinelike +pinkishwhite +pinstripe +pinstriped +pintle +piperata +piperita +pipetting +pipework +piranhas +piscivorus +piste +pitiful +placegetters +planers +planetoid +planicollis +planispirally +planked +plasmin +plastering +platformagnostic +platformpuzzle +plats +plaumanni +playercreated +playhouses +playonwords +playschool +playscript +pleco +pleinair +pleopods +plethysmography +pleurisy +plexiform +poensis +poetsaint +pointillism +pointofpurchase +pokeweed +polarities +poled +polenta +policyrelated +polingi +pollarded +pollards +polyamides +polychroma +polycomb +polyelectrolyte +polymerbased +polypectomy +polypoid +pompadour +ponderous +ponerine +pooram +popart +popcountry +popera +poprap +populariser +populists +porcellus +porphyroblasts +portalRobert +portalWilliam +portent +postBeatles +postBritpop +postKatrina +postabortion +postcanine +postcapitalist +postcrisis +postcyberpunk +postfeminism +posthole +postoperatively +postseasons +pouchlike +powwows +ppi +praenomina +preBroadway +preConfederation +preamps +preapproval +precognitive +precuneus +predispositions +predrilled +preevent +preferment +prehuman +premRNAs +premenstrual +prequalification +prescriptiononly +preselector +presenilin +presidentCEO +presolar +presuppose +presupposed +pretest +prez +pricei +primase +printf +proPalestinian +problematica +problembased +procommunist +prodromal +producercomposer +proficiencies +profligate +progun +prohormones +projectional +prolixus +proloculus +propanediol +propertycasualty +proplyds +propodeal +propodeum +proselytization +proteasomes +proteinDNA +proteinbased +protem +protocarnivorous +protolanguages +protolith +protostars +protosuchian +provenience +provincials +pry +przewalskii +pseudepigraphic +pseudohistory +pseudopods +pseudowire +psychobiology +psychographic +psychokinesis +psychokinetic +psycholinguistic +pu +publichealth +publicrelations +puerile +puklo +pullata +pulloff +punctifrons +punctiventris +punditry +purifies +purlins +purpleglossed +purplethroated +purpleveined +pursuer +pursuivant +purveyed +pushchairs +pushover +putamen +puzzleplatformer +pyrrolidine +quadrifidus +quadrivium +quadruplets +quaestors +quagmire +quandong +quantitation +quarrelsome +quarterbacked +quarterlies +quasilegal +quasiofficial +quickservice +quiero +quieting +quitrent +quizbowl +quorums +radiatum +radioshow +radular +raffrayi +rages +railguns +railtour +railyards +rainbowcolored +ramjets +ramsayi +ramsons +ranchera +rangefinders +ranita +rapa +rapidacting +rapidresponse +rappel +ratcatcher +ravelin +ravers +rayed +rdlargest +rdparty +rds +reaccreditation +readapted +reals +reaming +reanimate +reappoint +reappropriated +rearm +reassignments +reattributed +reawakened +reawakening +recapitalization +recasts +recline +recliner +recoiloperated +recollect +recollected +recondita +reconnoiter +reconstituting +reconversion +recrimination +rectangularshaped +recuperative +recusancy +recyclables +recycler +redbay +redblue +redbodied +redbud +redchested +redcrested +reddelli +redfooted +rednecks +redressing +reductant +redviolet +reefbuilding +reengage +reevaluating +refills +reframe +refreshable +refulgens +regenerator +regionalized +regionbased +regnum +regurgitate +rehabilitates +rehash +reimbursable +reincarnate +reincorporation +reinstall +reiterates +relabelled +relapsingremitting +relativist +remelting +remiges +remits +remitting +remunerated +renews +replenishes +replevin +reprieves +reprimands +reprintings +reregister +rerigged +resected +reseeded +reshoot +residentially +resp +restate +restricta +restructures +resupplying +resurveyed +retakes +retiarius +retinas +retool +retractions +retransmitting +retrofits +retrotransposon +returnable +revenant +reverent +reversa +reverser +rhatany +rhineurids +rhizospheres +rhombifer +richardi +rideshare +ridgeway +rightbranching +rightsholder +rigsdaler +rioni +roadbased +roadlegal +roadmaps +rochet +rockhard +rockinspired +rockpostpunk +rockreggae +roled +rollouts +romancing +romantics +roqueforti +rosaries +rotisserie +rotoscoped +roughest +roundeared +rubriventris +rubythroat +ruffles +rufousfronted +rufoussided +rugulosa +rumah +runandgun +russelli +sTLD +sabbaticals +sabulosa +saccades +saccharolytic +saccharum +sachalinensis +sacristan +saddlery +saddleshaped +safehouse +sagitta +saillike +sailmakers +salebrosus +salicylates +salinarum +salivarius +salmonellosis +salmonfly +saltwort +salvia +samizdat +sandflies +sandplains +sandspit +sanidine +sants +saponification +sargentii +satchel +savez +sawedoff +scaffoldin +scaleless +scalene +scalywinged +scarecrows +scarps +schaeferi +scheffleri +schemer +schiffornis +schilbid +schistacea +schistosomes +schizoid +schwannomas +schweinfurthii +sci +sciatica +scienceoriented +scififantasy +sclerotherapy +scolding +scone +scoparium +scorekeeping +scrambler +scratchoff +screamers +screeners +screenprinted +screeves +scribed +scribing +scrimshaw +scrivener +scrummager +scud +scudderi +scullers +sculptilis +sculpturatus +scythes +seaice +sealskin +seasnake +seborrheic +secessions +sechellarum +secondconsecutive +secreta +seductress +seedbed +segueing +sein +seismometer +selfabsorption +selfacting +selfassessed +selfconfident +selfdoubt +selfenhancement +selfextracting +selfheal +selfinjury +selflocking +selfmanaging +selfportraiture +selfpublishes +selfreflective +selfreports +selfrestraint +selfsealing +selfselected +selfsupport +selfsynchronizing +selftrained +sellata +sellside +semiabstract +semibrunnea +semicircles +semiconservative +semidry +semifast +seminigra +semireality +semiregularly +semitractor +sensitisation +sensus +sentinels +septostomy +seraph +seraphim +serenata +serinethreoninespecific +setal +setoff +setulosa +sevenacre +sevencounty +sevenseat +sevenseater +seventysixth +sexier +sexpunctatus +seychellarum +shafferi +shameless +sharable +sharpnosed +shastra +shavers +shawarma +shea +sheave +shebeens +shelducks +shepherdess +shied +shikimic +shipwrights +shogunate +shortduration +shorthead +shortlasting +shortsighted +shorttime +shovelers +shrewrat +shruti +shura +sibia +sibiricum +siddha +sidebars +sideburns +sidechains +sidegill +sidemounted +sidestick +sidewinder +sieberi +sightseers +signalmen +signore +signsoff +silene +siliciclastic +silicosis +siliques +sillimanite +silvermedal +sima +similarlytitled +similes +simulationbased +singlefield +singlegender +singleleaf +singlepurpose +singlestep +singlewalled +sinistra +sinuate +siphonophore +sis +sistergroup +sisterstation +situating +sixacre +sixalbum +sixcarbon +sixspotted +sixthhighest +skacore +skeptically +skiathlon +skittles +skydive +slabsided +slatybacked +slaving +sledging +sleepaway +slewing +slimmest +slipcover +slowburning +slowtempo +smallcaliber +smallmarket +smallscaled +smiled +smug +smuts +snappy +snelleni +snowcats +snowdrops +snowplows +sobria +socioecological +sociolegal +socken +sodalis +softwareonly +sogno +sojae +solaris +soloed +somalicus +somatotropin +somites +songanddance +sop +sophia +sorely +sorters +sortes +sotol +soubrette +souldisco +soundcard +soundeffects +sourness +souschef +southeasterncentral +spacegrant +spacerock +sparrowsized +spectroscopically +spectroscopist +speedier +speleology +spermatophores +spermicide +sphaerica +sphaericus +sphingolipids +spiculum +spiderlily +spiker +spiketail +spinART +spinae +spinetipped +spinigera +spinneret +spinsters +spirometry +spittlebugs +splitreel +spongiosum +sportsdrama +sprawls +spurwinged +spymaster +squadra +squib +stagnating +stairstep +stalkless +stampings +stan +starched +starfield +starfighter +stargazers +statebred +staybehind +stearothermophilus +steelreinforced +steelworker +steeplechasers +steerage +stegosaur +stellatum +stenographers +stenopetala +stenotype +stephani +stepparent +stercoralis +sterilis +sternoclavicular +stewarding +stickball +stillextant +stingy +stipitatum +stlargest +stoa +stockier +stoke +stoptime +storksbill +stovetop +stowaways +straightbilled +straighteight +straightsnouted +straightwing +stranka +streetfighter +strenuus +stresemanni +striatella +strippedback +stroboscope +strongpoints +studentbased +studentdirected +studioalbum +studiooffice +stunningly +sturgeons +stygia +subLOGIC +subaquatic +subaqueous +subband +subbrands +subcosmopolitan +subdirector +subentity +subgen +subhuman +subjunctive +subleased +sublingua +submarket +submillimetre +subpost +subscript +subscripts +subsisted +subspaces +substantiation +subthalamus +subtleties +suburbanites +subvillages +subwavelength +succubi +sudanensis +suduirauti +sugaring +sulcatum +summiting +summitted +sumptuosa +superblock +supercup +superego +superfluidity +superfluids +superimposes +superimposing +supermoon +supernaturally +supertanker +superyachts +supracondylar +supralibros +supratrochlear +surfacesupplied +surficial +surfperches +swaggering +swainsonii +swindles +swinhoei +swiped +swiss +switchback +swooping +swordfighting +swordgrass +sympatry +syncretistic +syncretized +synovitis +synthdriven +synthheavy +syriacus +systemspecific +tabloidsize +tabulations +taczanowskii +tahitensis +tailshield +talbotii +talismanic +tambourines +tamsi +tangleweb +taphonomic +taskspecific +tastefully +tattooist +technocracy +technoscience +tectus +tegument +tela +teleported +televisionfilm +telugu +temperaturesensitive +tenable +tenellum +tenesmus +tenfoot +tensong +tera +teramachii +terga +terminologyoriented +termly +ternata +terraformed +terreus +testability +testaceipes +testaceum +testbench +tetrads +tetrahydrofuran +tetramers +textmessaging +textually +thenBritish +thenMinister +thencontroversial +thenknown +thenongoing +thensister +theobromae +thermalis +thermite +thermochromic +thermography +thermohaline +theyyam +thhighestgrossing +thiazole +thimerosal +thinskinned +thirdever +thirdfloor +thirdranked +thirdsmallest +thirdtallest +threadsafe +threecolored +threeline +threenote +threeposition +threeseater +threshers +thrifts +throb +thromboembolic +throttled +thymosin +tilling +tiltwing +timberlands +timbral +tintinnabulum +tipoff +titania +titillation +titleThe +tody +togoensis +tollways +tondo +toneddown +tonsillar +toolpath +toon +topdressing +topmounted +topspin +torchbearers +tornadic +tornados +torogan +tou +toulgoeti +townscape +toxopei +toying +tp +tracheobronchial +trackandfield +trackwork +tradein +trademarking +tramlines +tranquilizers +transcendentalism +transferral +transfused +transhuman +transhumance +transience +translatable +transliterating +transload +transloading +translocates +translunar +transmissive +transnationally +transpacific +transpires +transplantations +transverses +transvestism +trashy +trax +treader +trebles +treefrogs +treehunter +treering +treetop +trehalose +trellises +triakis +triaugmented +tribosphenic +tribunician +trichloroethane +trichomoniasis +tricorne +trifolia +trilateration +trilling +trilobata +trinitrate +triphenylchloroethylene +tripwires +triteleia +tritone +trochaic +trochanteric +troglobites +troublemakers +trustfund +truthvalue +tryoni +trypsinlike +tsars +tserkvas +tsugae +tubericollis +tucking +tugged +tularemia +tuneup +tunicle +turanica +turbinata +turbot +turnbowi +tutta +tvs +tweeters +twelveepisode +twelveminute +twentyfirstcentury +twinrotor +twinturbo +twistedpair +twitter +twobarred +twoblock +twocandidate +twodecade +twofaced +twofifths +twoforone +twoleg +twolight +twospan +twospirit +twostoried +twostringed +twothree +twoweeklong +twowheeler +twoword +tyer +typeA +typifying +typos +tyrannosaur +tyrannosauroid +tyrannosaurs +ulcerated +ulei +ultrasoundguided +ultrasounds +umbellatum +umbilicate +umbraculum +unachievable +unbalance +unbanned +unbleached +unblocked +unbroadcast +unclassifiable +unclothed +undemanding +underbrush +underlings +undernourished +undershot +understatement +understeer +understudying +undervalue +undosa +unformed +unfortified +unicolored +unicyclist +unifascia +uniflorum +unionizing +unitized +universityaffiliated +unlistenable +unmonitored +unnerving +unornamented +unpasteurised +unpronounceable +unrolling +unsaturation +unscented +unsorted +unsurprising +untalented +untangle +untaxed +untoothed +unwholesome +uomo +updatable +upended +upfield +upperbody +upscaled +upselling +uptick +urethritis +uropeltid +uropods +userselected +ute +vaccinating +vaccinology +vacuumchannel +vacuumtube +vaginally +vallecula +valuesbased +vampirethemed +vanquish +variata +variates +varipes +varus +vasodilators +vectorized +vel +venipuncture +ventilatory +verismo +vermiculatus +vermiculite +vernalgrass +verrilli +versioned +vervet +vesicatoria +vespid +vetus +vetusta +vexans +vexillological +vibrans +viceroyalty +videodisk +vigintiviri +viharas +vilis +villosum +vimana +vinaigrette +vinegars +vinelike +viscosus +vitrinite +vittigera +vivesi +viviparity +vivir +vocalistsongwriter +vocalskeyboards +voi +voiceactivated +voicetracked +voir +volleyballer +volubilis +vomited +voyagers +voyeur +vulgarism +vulval +wada +waddling +wale +walkietalkies +wallaroo +walsinghami +warmups +warmweather +washable +waterhousei +watertable +wavering +waxcaps +weaponsgrade +weatherboarding +weathercaster +webMethods +webtoons +webzines +wedgeleaf +wellproportioned +wellwatered +werejaguar +westerner +wetware +whitecapped +whitechinned +whitings +wholegrain +whoopee +wicking +wilfordii +windbreaks +windpollinated +windpowered +windsurf +windtunnel +winerys +wingbars +wireworm +wisteria +wkndstv +woodcarvings +woodchip +woodcraft +woodfordi +woodi +woodlot +woodlots +woodswallow +wooed +wools +woolstore +workgroups +worksite +wormeating +wows +writeonce +writeractor +wrongdoers +wryly +ws +wunderkind +wurdackii +wurrung +xaxis +xenarthrans +xenos +xenotransplantation +xyloglucan +yellowcress +yellowfooted +yellowishgray +yellowthroat +yi +yn +yodel +yodeler +yogurts +yuca +yunnana +yuppie +zari +zealandica +zealot +zerocarbon +zeroemission +zerotolerance +zeteki +zhejiangensis +ziemia +zimmermanni +zircons +zombified +zonalis +zonation +zorro +ztl diff --git a/app/src/main/java/com/example/myapplication/AppContext.kt b/app/src/main/java/com/example/myapplication/AppContext.kt new file mode 100644 index 0000000..ab11a18 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/AppContext.kt @@ -0,0 +1,12 @@ +package com.example.myapplication + +import android.content.Context + +object AppContext { + lateinit var context: Context + private set + + fun init(ctx: Context) { + context = ctx.applicationContext + } +} diff --git a/app/src/main/java/com/example/myapplication/GuideActivity.kt b/app/src/main/java/com/example/myapplication/GuideActivity.kt index 97c1a22..af4aa1f 100644 --- a/app/src/main/java/com/example/myapplication/GuideActivity.kt +++ b/app/src/main/java/com/example/myapplication/GuideActivity.kt @@ -24,6 +24,7 @@ import androidx.core.content.ContextCompat import android.widget.ImageView import android.text.TextWatcher import android.text.Editable +import com.example.myapplication.network.BehaviorReporter class GuideActivity : AppCompatActivity() { @@ -91,12 +92,22 @@ class GuideActivity : AppCompatActivity() { } // 情话复制 findViewById(R.id.love_words_1).setOnClickListener { + BehaviorReporter.report( + isNewUser = false, + "page_id" to "guide", + "element_id" to "copy_example_1", + ) val text = it as TextView val clipboard = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager clipboard.setPrimaryClip(ClipData.newPlainText("text", text.text)) Toast.makeText(this, "Copy successfully", Toast.LENGTH_SHORT).show() } findViewById(R.id.love_words_2).setOnClickListener { + BehaviorReporter.report( + isNewUser = false, + "page_id" to "guide", + "element_id" to "copy_example_2", + ) val text = it as TextView val clipboard = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager clipboard.setPrimaryClip(ClipData.newPlainText("text", text.text)) diff --git a/app/src/main/java/com/example/myapplication/ImeGuideActivity.kt b/app/src/main/java/com/example/myapplication/ImeGuideActivity.kt index c54095e..ab1afd5 100644 --- a/app/src/main/java/com/example/myapplication/ImeGuideActivity.kt +++ b/app/src/main/java/com/example/myapplication/ImeGuideActivity.kt @@ -12,6 +12,7 @@ import android.widget.LinearLayout import android.widget.TextView import android.widget.Toast import androidx.appcompat.app.AppCompatActivity +import com.example.myapplication.network.BehaviorReporter class ImeGuideActivity : AppCompatActivity() { @@ -33,6 +34,11 @@ class ImeGuideActivity : AppCompatActivity() { super.onCreate(savedInstanceState) setContentView(R.layout.activity_ime_guide) + BehaviorReporter.report( + isNewUser = false, + "page_id" to "keyboard_permission_guide", + ) + Log.d(TAG, "onCreate") btnEnable = findViewById(R.id.enabled) // btn启用输入法 @@ -69,6 +75,15 @@ class ImeGuideActivity : AppCompatActivity() { } } + override fun onBackPressed() { + BehaviorReporter.report( + isNewUser = false, + "page_id" to "keyboard_permission_guide", + "element_id" to "close_btn", + ) + super.onBackPressed() + } + private fun registerImeObserver() { if (imeObserver != null) return @@ -153,6 +168,11 @@ class ImeGuideActivity : AppCompatActivity() { selectLayout.setOnClickListener { val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager imm.showInputMethodPicker() + BehaviorReporter.report( + isNewUser = false, + "page_id" to "keyboard_permission_guide", + "element_id" to "open_settings_btn", + ) } } @@ -192,6 +212,11 @@ class ImeGuideActivity : AppCompatActivity() { selectText.setTextColor(Color.parseColor("#A1A1A1")) step1.text = "Completed" step2.text = "You have completed the relevant Settings" + BehaviorReporter.report( + isNewUser = false, + "page_id" to "keyboard_permission_guide", + "element_id" to "close_btn", + ) Toast.makeText(this, "The input method is all set!", Toast.LENGTH_SHORT).show() try { startActivity(Intent(this, GuideActivity::class.java)) diff --git a/app/src/main/java/com/example/myapplication/MainActivity.kt b/app/src/main/java/com/example/myapplication/MainActivity.kt index 9afd912..d47bb31 100644 --- a/app/src/main/java/com/example/myapplication/MainActivity.kt +++ b/app/src/main/java/com/example/myapplication/MainActivity.kt @@ -1,6 +1,7 @@ package com.example.myapplication import android.os.Bundle +import android.util.Log import android.view.View import android.widget.Toast import androidx.activity.OnBackPressedCallback @@ -10,6 +11,7 @@ import androidx.navigation.NavController import androidx.navigation.fragment.NavHostFragment import com.example.myapplication.network.AuthEvent import com.example.myapplication.network.AuthEventBus +import com.example.myapplication.network.BehaviorReporter import com.example.myapplication.utils.EncryptedSharedPreferencesUtil import com.google.android.material.bottomnavigation.BottomNavigationView import kotlinx.coroutines.flow.collectLatest @@ -59,6 +61,45 @@ class MainActivity : AppCompatActivity() { private val globalNavController: NavController get() = globalHost.navController + // ======================= + // 全局路由埋点:新增字段 + // ======================= + private val ROUTE_TAG = "RouteReport" + + private var lastHomeDestIdForReport: Int? = null + private var lastShopDestIdForReport: Int? = null + private var lastMineDestIdForReport: Int? = null + private var lastGlobalDestIdForReport: Int? = null + + // 统一 listener,方便 add/remove + private val homeRouteListener = + NavController.OnDestinationChangedListener { _, dest, _ -> + if (lastHomeDestIdForReport == dest.id) return@OnDestinationChangedListener + lastHomeDestIdForReport = dest.id + reportPageView(source = "home_tab", destId = dest.id) + } + + private val shopRouteListener = + NavController.OnDestinationChangedListener { _, dest, _ -> + if (lastShopDestIdForReport == dest.id) return@OnDestinationChangedListener + lastShopDestIdForReport = dest.id + reportPageView(source = "shop_tab", destId = dest.id) + } + + private val mineRouteListener = + NavController.OnDestinationChangedListener { _, dest, _ -> + if (lastMineDestIdForReport == dest.id) return@OnDestinationChangedListener + lastMineDestIdForReport = dest.id + reportPageView(source = "mine_tab", destId = dest.id) + } + + private val globalRouteListener = + NavController.OnDestinationChangedListener { _, dest, _ -> + if (lastGlobalDestIdForReport == dest.id) return@OnDestinationChangedListener + lastGlobalDestIdForReport = dest.id + reportPageView(source = "global_overlay", destId = dest.id) + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) @@ -105,9 +146,11 @@ class MainActivity : AppCompatActivity() { openGlobal(R.id.loginFragment) } } + is AuthEvent.GenericError -> { - Toast.makeText(this@MainActivity, event.message, Toast.LENGTH_SHORT).show() + Toast.makeText(this@MainActivity, event.message, Toast.LENGTH_LONG).show() } + // 登录成功事件处理 is AuthEvent.LoginSuccess -> { // 关闭 global overlay:回到 empty @@ -123,24 +166,46 @@ class MainActivity : AppCompatActivity() { } } pendingTabAfterLogin = null + + // 处理intent跳转目标页 + if (pendingNavigationAfterLogin == "recharge_fragment") { + openGlobal(R.id.rechargeFragment) + pendingNavigationAfterLogin = null + } + + // ✅ 登录成功后也刷新一次 + bottomNav.post { updateBottomNavVisibility() } } + // 登出事件处理 is AuthEvent.Logout -> { pendingTabAfterLogin = event.returnTabTag - + // ✅ 用户没登录按返回,应回首页,所以先切到首页 switchTab(TAB_HOME, force = true) - + bottomNav.post { bottomNav.selectedItemId = R.id.home_graph openGlobal(R.id.loginFragment) // ✅ 退出登录后立刻打开登录页 } } + // 打开全局页面事件处理 is AuthEvent.OpenGlobalPage -> { - // 打开指定的全局页面 openGlobal(event.destinationId, event.bundle) } + + is AuthEvent.UserUpdated -> { + // 不需要处理 + } + + is AuthEvent.CharacterDeleted -> { + // 不需要处理 + } + + is AuthEvent.CharacterAdded -> { + // 不需要处理,由HomeFragment处理 + } } } } @@ -158,9 +223,27 @@ class MainActivity : AppCompatActivity() { TAB_MINE -> R.id.mine_graph else -> R.id.home_graph } + updateBottomNavVisibility() } } + override fun onResume() { + super.onResume() + // ✅ 最终兜底:从后台回来 / 某些场景没触发 listener,也能恢复底栏 + bottomNav.post { updateBottomNavVisibility() } + } + + override fun onDestroy() { + // ✅ 防泄漏:移除路由监听(Activity 销毁时) + runCatching { + homeHost.navController.removeOnDestinationChangedListener(homeRouteListener) + shopHost.navController.removeOnDestinationChangedListener(shopRouteListener) + mineHost.navController.removeOnDestinationChangedListener(mineRouteListener) + globalHost.navController.removeOnDestinationChangedListener(globalRouteListener) + } + super.onDestroy() + } + private fun initHosts() { val fm = supportFragmentManager @@ -196,22 +279,59 @@ class MainActivity : AppCompatActivity() { // 绑定全局导航可见性监听 bindGlobalVisibility() - + // 绑定底部导航栏可见性监听 bindBottomNavVisibilityForTabs() + + // ✅ 全局路由埋点监听(每次导航变化上报) + bindGlobalRouteReporting() + + bottomNav.post { updateBottomNavVisibility() } + } + + /** + * 这些页面需要隐藏底部导航栏:你按需加/减 + */ + private fun shouldHideBottomNav(destId: Int): Boolean { + return destId in setOf( + R.id.searchFragment, + R.id.searchResultFragment, + R.id.MySkin, + R.id.notificationFragment, + R.id.feedbackFragment, + R.id.MyKeyboard, + R.id.PersonalSettings, + ) + } + + /** + * ✅ 统一底栏显隐逻辑:任何地方状态变化都调用它 + */ + private fun updateBottomNavVisibility() { + // ✅ 只要 global overlay 不在 empty,底栏必须隐藏(用 NavController 判断,别用 View.visibility) + if (isGlobalVisible()) { + bottomNav.visibility = View.GONE + return + } + + // 否则按“当前可见 tab 的当前目的地”判断 + val destId = currentTabNavController.currentDestination?.id + bottomNav.visibility = + if (destId != null && shouldHideBottomNav(destId)) View.GONE else View.VISIBLE } private fun bindGlobalVisibility() { globalNavController.addOnDestinationChangedListener { _, dest, _ -> val isEmpty = dest.id == R.id.globalEmptyFragment - - findViewById(R.id.global_container).visibility = - if (isEmpty) View.GONE else View.VISIBLE - bottomNav.visibility = - if (isEmpty) View.VISIBLE else View.GONE - // ✅ 只在"刚从某个全局页关闭回 empty"时触发回退逻辑 - val justClosedOverlay = (dest.id == R.id.globalEmptyFragment && lastGlobalDestId != R.id.globalEmptyFragment) + findViewById(R.id.global_container).visibility = + if (isEmpty) View.GONE else View.VISIBLE + + // ✅ 底栏统一走 update + updateBottomNavVisibility() + + val justClosedOverlay = + (dest.id == R.id.globalEmptyFragment && lastGlobalDestId != R.id.globalEmptyFragment) lastGlobalDestId = dest.id if (justClosedOverlay) { @@ -220,16 +340,17 @@ class MainActivity : AppCompatActivity() { TAB_MINE -> R.id.mine_graph else -> R.id.home_graph } - // 未登录且当前处在受保护tab:强制回首页 + if (!isLoggedIn() && currentTabGraphId in protectedTabs) { switchTab(TAB_HOME, force = true) bottomNav.selectedItemId = R.id.home_graph } - // ✅ 只有"没登录就关闭登录页"才清 pending if (!isLoggedIn()) { pendingTabAfterLogin = null } + + bottomNav.post { updateBottomNavVisibility() } } } } @@ -238,7 +359,7 @@ class MainActivity : AppCompatActivity() { if (!force && targetTag == currentTabTag) return val fm = supportFragmentManager - if (fm.isStateSaved) return // ✅ 防崩:stateSaved 时不做事务 + if (fm.isStateSaved) return currentTabTag = targetTag @@ -255,57 +376,80 @@ class MainActivity : AppCompatActivity() { } } .commit() + + // ✅ 关键:hide/show 切 tab 不会触发 destinationChanged,所以手动刷新 + bottomNav.post { updateBottomNavVisibility() } + + // ✅ 新增:切 tab 后补一次路由上报(不改变其它逻辑) + if (!force) { + currentTabNavController.currentDestination?.id?.let { destId -> + reportPageView(source = "switch_tab", destId = destId) + } + } } /** 打开全局页(login/recharge等) */ private fun openGlobal(destId: Int, bundle: Bundle? = null) { val fm = supportFragmentManager - if (fm.isStateSaved) return // ✅ 防崩 + if (fm.isStateSaved) return try { - if (bundle != null) { - globalNavController.navigate(destId, bundle) - } else { - globalNavController.navigate(destId) - } + if (bundle != null) globalNavController.navigate(destId, bundle) + else globalNavController.navigate(destId) } catch (e: IllegalArgumentException) { - // 可选:防止偶发重复 navigate 崩溃 e.printStackTrace() } + + bottomNav.post { updateBottomNavVisibility() } } - /** 关闭全局页:pop到 empty */ + /** Tab 内页面变化时刷新底栏显隐 */ private fun bindBottomNavVisibilityForTabs() { - fun shouldHideBottomNav(destId: Int): Boolean { - return destId in setOf( - R.id.searchFragment, - R.id.searchResultFragment, - R.id.MySkin - // 你还有其他需要全屏的页,也加在这里 - ) + val listener = NavController.OnDestinationChangedListener { _, _, _ -> + updateBottomNavVisibility() } - val listener = NavController.OnDestinationChangedListener { _, dest, _ -> - // 只要 global overlay 打开了,仍然以 overlay 为准(你已有逻辑) - if (isGlobalVisible()) return@OnDestinationChangedListener - bottomNav.visibility = if (shouldHideBottomNav(dest.id)) View.GONE else View.VISIBLE - } - homeHost.navController.addOnDestinationChangedListener(listener) shopHost.navController.addOnDestinationChangedListener(listener) mineHost.navController.addOnDestinationChangedListener(listener) + BehaviorReporter.report( + isNewUser = false, + "page_id" to "home", + ) + } + + // ✅ 绑定全局路由埋点(四个 NavController) + private fun bindGlobalRouteReporting() { + homeHost.navController.addOnDestinationChangedListener(homeRouteListener) + shopHost.navController.addOnDestinationChangedListener(shopRouteListener) + mineHost.navController.addOnDestinationChangedListener(mineRouteListener) + globalHost.navController.addOnDestinationChangedListener(globalRouteListener) + + // ✅ 删除:初始化手动上报(否则启动时会重复上报) + // runCatching { + // currentTabNavController.currentDestination?.id?.let { reportPageView("init_current_tab", it) } + // globalNavController.currentDestination?.id?.let { reportPageView("init_global", it) } + // } } private fun closeGlobalIfPossible(): Boolean { if (!isGlobalVisible()) return false val popped = globalNavController.popBackStack() - val stillVisible = globalNavController.currentDestination?.id != R.id.globalEmptyFragment - return popped || stillVisible + + // ✅ pop 后刷新一次(注意:currentDestination 可能要等一帧才更新,所以 post) + bottomNav.post { updateBottomNavVisibility() } + + // popped = true 表示确实 pop 了;即使 popped=false 也可能已经在 empty 了 + return popped || !isGlobalVisible() } + /** + * ✅ 改这里:不要再用 View.visibility 判断 overlay + * 以 NavController 的目的地为准 + */ private fun isGlobalVisible(): Boolean { - return findViewById(R.id.global_container).visibility == View.VISIBLE + return globalNavController.currentDestination?.id != R.id.globalEmptyFragment } private fun setupBackPress() { @@ -316,14 +460,15 @@ class MainActivity : AppCompatActivity() { // 2) 再 pop 当前tab val popped = currentTabNavController.popBackStack() - if (popped) return + if (popped) { + bottomNav.post { updateBottomNavVisibility() } + return + } // 3) 当前tab到根了:如果不是home,切回home;否则退出 - if (currentTabTag != TAB_HOME) { - bottomNav.post { - bottomNav.selectedItemId = R.id.home_graph - } - switchTab(TAB_HOME) + if (currentTabTag != TAB_HOME) { + bottomNav.post { bottomNav.selectedItemId = R.id.home_graph } + switchTab(TAB_HOME) } else { finish() } @@ -331,17 +476,25 @@ class MainActivity : AppCompatActivity() { }) } + private var pendingNavigationAfterLogin: String? = null + private fun handleNavigationFromIntent() { val navigateTo = intent.getStringExtra("navigate_to") if (navigateTo == "recharge_fragment") { bottomNav.post { if (!isLoggedIn()) { + pendingNavigationAfterLogin = navigateTo openGlobal(R.id.loginFragment) - return@post + return@post } openGlobal(R.id.rechargeFragment) } } + if (navigateTo == "login_fragment") { + bottomNav.post { + openGlobal(R.id.loginFragment) + } + } } private fun isLoggedIn(): Boolean { @@ -352,4 +505,68 @@ class MainActivity : AppCompatActivity() { outState.putString("current_tab_tag", currentTabTag) super.onSaveInstanceState(outState) } -} \ No newline at end of file + + // ======================= + // 全局路由埋点:page_id 映射 + 上报 + // ======================= + + private fun pageIdForDest(destId: Int): String { + return when (destId) { + + /** ==================== 首页 Home ==================== */ + R.id.homeFragment -> "home_main" // 首页-主页面 + R.id.keyboardDetailFragment -> "skin_detail" // 键盘详情页 + R.id.MyKeyboard -> "my_keyboard" // 键盘设置 + + /** ==================== 商城 Shop ==================== */ + R.id.shopFragment -> "shop" // 商城首页 + R.id.searchFragment -> "search" // 搜索页 + R.id.searchResultFragment -> "search_result" // 搜索结果页 + R.id.MySkin -> "my_skin" // 我的皮肤 + + /** ==================== 我的 Mine ==================== */ + R.id.mineFragment -> "my" // 我的-首页 + R.id.PersonalSettings -> "person_info" // 个人设置 + R.id.notificationFragment -> "notice" // 消息通知 + R.id.feedbackFragment -> "feedback" // 意见反馈 + R.id.consumptionRecordFragment -> "consumption_record" // 消费记录 + + /** ==================== 登录 & 注册 ==================== */ + R.id.loginFragment -> "login" // 登录页 + R.id.registerFragment -> "register_email" // 注册页 + R.id.registerVerifyFragment -> "register_verify_email" // 注册验证码 + R.id.forgetPasswordEmailFragment -> "forgot_password_email" // 忘记密码-邮箱 + R.id.forgetPasswordVerifyFragment -> "forgot_password_verify" // 忘记密码-验证码 + R.id.forgetPasswordResetFragment -> "forgot_password_newpwd" // 忘记密码-重置密码 + + /** ==================== 充值相关 ==================== */ + R.id.rechargeFragment -> "vip_pay" // 充值首页 + R.id.goldCoinRechargeFragment -> "points_recharge" // 金币充值 + + /** ==================== 全局 / 占位 ==================== */ + R.id.globalEmptyFragment -> "global_empty" // 全局占位页(兜底) + + /** ==================== 兜底处理 ==================== */ + else -> "unknown_$destId" // 未配置的页面,方便排查遗漏 + } + } + + private fun reportPageView(source: String, destId: Int) { + val pageId = pageIdForDest(destId) + if (destId == R.id.globalEmptyFragment) return + if (destId == R.id.loginFragment || destId == R.id.registerFragment){ + BehaviorReporter.report( + isNewUser = true, + "page_id" to pageId, + ) + return + } + + Log.d(ROUTE_TAG, "route: source=$source destId=$destId page_id=$pageId") + + BehaviorReporter.report( + isNewUser = false, + "page_id" to pageId, + ) + } +} diff --git a/app/src/main/java/com/example/myapplication/MyApp.kt b/app/src/main/java/com/example/myapplication/MyApp.kt index 8df53e8..ac6c030 100644 --- a/app/src/main/java/com/example/myapplication/MyApp.kt +++ b/app/src/main/java/com/example/myapplication/MyApp.kt @@ -2,13 +2,16 @@ package com.example.myapplication import android.app.Application import com.example.myapplication.network.RetrofitClient +import com.example.myapplication.network.NetworkClient class MyApp : Application() { override fun onCreate() { super.onCreate() - // 初始化 RetrofitClient,传入 ApplicationContext + AppContext.init(this) // ✅ 新增:全局 Application Context + RetrofitClient.init(this) + NetworkClient.init(this) // ✅ SSE 用(带 token/签名拦截器) } } diff --git a/app/src/main/java/com/example/myapplication/MyInputMethodService.kt b/app/src/main/java/com/example/myapplication/MyInputMethodService.kt index 6530edb..6832e27 100644 --- a/app/src/main/java/com/example/myapplication/MyInputMethodService.kt +++ b/app/src/main/java/com/example/myapplication/MyInputMethodService.kt @@ -46,6 +46,8 @@ import android.graphics.drawable.GradientDrawable import kotlin.math.abs import java.text.BreakIterator import android.widget.EditText +import android.content.res.Configuration +import androidx.constraintlayout.widget.ConstraintLayout class MyInputMethodService : InputMethodService(), KeyboardEnvironment { @@ -264,23 +266,6 @@ class MyInputMethodService : InputMethodService(), KeyboardEnvironment { createNotificationChannelIfNeeded() tryStartForegroundSafe() - - // 监听认证事件 - // CoroutineScope(Dispatchers.Main).launch { - // AuthEventBus.events.collectLatest { event -> - // if (event is AuthEvent.TokenExpired) { - // // 启动 MainActivity 并跳转到登录页面 - // val intent = Intent(this@MyInputMethodService, MainActivity::class.java).apply { - // flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK - // putExtra("navigate_to", "loginFragment") - // } - // startActivity(intent) - // } else if (event is AuthEvent.GenericError) { - // // 显示错误提示 - // android.widget.Toast.makeText(this@MyInputMethodService, "请求失败: ${event.message}", android.widget.Toast.LENGTH_SHORT).show() - // } - // } - // } } // 输入法状态变化 @@ -319,6 +304,7 @@ class MyInputMethodService : InputMethodService(), KeyboardEnvironment { val keyboard = ensureMainKeyboard() currentKeyboardView = keyboard.rootView mainKeyboardView = keyboard.rootView + (keyboard.rootView.parent as? ViewGroup)?.removeView(keyboard.rootView) return keyboard.rootView } @@ -405,7 +391,7 @@ class MyInputMethodService : InputMethodService(), KeyboardEnvironment { // 初始状态:隐藏联想条,显示控制面板 mainKeyboardView - ?.findViewById(R.id.completion_scroll) + ?.findViewById(R.id.completion_scroll) ?.visibility = View.GONE mainKeyboardView @@ -604,15 +590,19 @@ class MyInputMethodService : InputMethodService(), KeyboardEnvironment { if (aiKeyboard == null) { aiKeyboard = AiKeyboard(this) aiKeyboardView = aiKeyboard!!.rootView + + // ✅ AI 键盘 Delete 按钮也绑定“长按连删 + 上滑清空” + val delId = resources.getIdentifier("keyboard_button_Delete", "id", packageName) + aiKeyboardView?.findViewById(delId)?.let { attachRepeatDeleteInternal(it) } } return aiKeyboard!! } - + override fun showMainKeyboard() { clearEditorState() val kb = ensureMainKeyboard() currentKeyboardView = kb.rootView - setInputView(kb.rootView) + setInputViewSafely(kb.rootView) kb.applyTheme(currentTextColor, currentBorderColor, currentBackgroundColor) } @@ -620,7 +610,7 @@ class MyInputMethodService : InputMethodService(), KeyboardEnvironment { clearEditorState() val kb = ensureNumberKeyboard() currentKeyboardView = kb.rootView - setInputView(kb.rootView) + setInputViewSafely(kb.rootView) kb.applyTheme(currentTextColor, currentBorderColor, currentBackgroundColor) } @@ -628,7 +618,7 @@ class MyInputMethodService : InputMethodService(), KeyboardEnvironment { clearEditorState() val kb = ensureSymbolKeyboard() currentKeyboardView = kb.rootView - setInputView(kb.rootView) + setInputViewSafely(kb.rootView) kb.applyTheme(currentTextColor, currentBorderColor, currentBackgroundColor) } @@ -636,18 +626,48 @@ class MyInputMethodService : InputMethodService(), KeyboardEnvironment { clearEditorState() val kb = ensureAiKeyboard() currentKeyboardView = kb.rootView - setInputView(kb.rootView) + setInputViewSafely(kb.rootView) kb.applyTheme(currentTextColor, currentBorderColor, currentBackgroundColor) + kb.refreshPersonas() } override fun showEmojiKeyboard() { clearEditorState() val kb = ensureEmojiKeyboard() currentKeyboardView = kb.rootView - setInputView(kb.rootView) + setInputViewSafely(kb.rootView) kb.applyTheme(currentTextColor, currentBorderColor, currentBackgroundColor) } + override fun associateClose() { + clearEditorState() + val kb = ensureEmojiKeyboard() + } + + override fun onConfigurationChanged(newConfig: Configuration) { + // 先清理缓存,避免复用旧 View + currentKeyboardView = null + + mainKeyboardView = null + numberKeyboardView = null + symbolKeyboardView = null + aiKeyboardView = null + emojiKeyboardView = null + + mainKeyboard = null + numberKeyboard = null + symbolKeyboard = null + aiKeyboard = null + emojiKeyboard = null + + super.onConfigurationChanged(newConfig) + } + + private fun setInputViewSafely(v: View) { + (v.parent as? ViewGroup)?.removeView(v) + super.setInputView(v) + } + // Emoji 键盘 private fun ensureEmojiKeyboard(): com.example.myapplication.keyboard.EmojiKeyboard { if (emojiKeyboard == null) { @@ -943,7 +963,7 @@ class MyInputMethodService : InputMethodService(), KeyboardEnvironment { // 新增:联想滚动条 & 控制面板 val completionScroll = - mainKeyboardView?.findViewById(R.id.completion_scroll) + mainKeyboardView?.findViewById(R.id.completion_scroll) val controlLayout = mainKeyboardView?.findViewById(R.id.control_layout) @@ -1006,7 +1026,7 @@ class MyInputMethodService : InputMethodService(), KeyboardEnvironment { // 自动滚回到最左边 private fun scrollSuggestionsToStart() { - val sv = mainKeyboardView?.findViewById(R.id.completion_scroll) + val sv = mainKeyboardView?.findViewById(R.id.completion_HorizontalScrollView) sv?.post { sv.fullScroll(View.FOCUS_LEFT) } } @@ -1402,7 +1422,7 @@ class MyInputMethodService : InputMethodService(), KeyboardEnvironment { // 4. UI:联想条隐藏 & 控制面板显示 mainHandler.post { val completionScroll = - mainKeyboardView?.findViewById(R.id.completion_scroll) + mainKeyboardView?.findViewById(R.id.completion_scroll) val controlLayout = mainKeyboardView?.findViewById(R.id.control_layout) diff --git a/app/src/main/java/com/example/myapplication/OnboardingActivity.kt b/app/src/main/java/com/example/myapplication/OnboardingActivity.kt index b56863f..5bb2dc3 100644 --- a/app/src/main/java/com/example/myapplication/OnboardingActivity.kt +++ b/app/src/main/java/com/example/myapplication/OnboardingActivity.kt @@ -3,16 +3,64 @@ package com.example.myapplication import android.content.Intent import android.os.Bundle import android.widget.Button +import android.widget.LinearLayout import android.widget.TextView import androidx.appcompat.app.AppCompatActivity +import com.example.myapplication.utils.EncryptedSharedPreferencesUtil +import android.view.ViewGroup +import android.widget.Toast +import com.example.myapplication.ui.common.LoadingOverlay +import android.os.Handler +import android.os.Looper class OnboardingActivity : AppCompatActivity() { + private var selectedGender = -1 // 0: male, 1: female, 2: third override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_onboarding) val btnStart = findViewById(R.id.tv_skip) + val maleLayout = findViewById(R.id.male_layout) + val femaleLayout = findViewById(R.id.female_layout) + val thirdLayout = findViewById(R.id.third_layout) + val tvDescription = findViewById(R.id.tv_description) + + // 设置性别选择点击事件 + maleLayout.setOnClickListener { + resetAllLayouts() + maleLayout.setBackgroundResource(R.drawable.gender_background_select) + selectedGender = 0 + } + + femaleLayout.setOnClickListener { + resetAllLayouts() + femaleLayout.setBackgroundResource(R.drawable.gender_background_select) + selectedGender = 1 + } + + thirdLayout.setOnClickListener { + resetAllLayouts() + thirdLayout.setBackgroundResource(R.drawable.gender_background_select) + selectedGender = 2 + } + + tvDescription.setOnClickListener { + if (selectedGender != -1) { + // 这里可以获取selectedGender的值(0,1,2) + // 标记已经不是第一次启动了 + val prefs = getSharedPreferences("app_prefs", MODE_PRIVATE) + prefs.edit().putBoolean("is_first_launch", false).apply() + EncryptedSharedPreferencesUtil.save(this, "gender", selectedGender.toString()) + // 跳转到主界面 + val rootView = window.decorView.findViewById(android.R.id.content) + LoadingOverlay.attach(rootView).show() + startActivity(Intent(this, MainActivity::class.java)) + finish() + }else{ + Toast.makeText(this, "Please select your gender.", Toast.LENGTH_SHORT).show() + } + } btnStart.setOnClickListener { // 标记已经不是第一次启动了 @@ -20,8 +68,16 @@ class OnboardingActivity : AppCompatActivity() { prefs.edit().putBoolean("is_first_launch", false).apply() // 跳转到主界面 + val rootView = window.decorView.findViewById(android.R.id.content) + LoadingOverlay.attach(rootView).show() startActivity(Intent(this, MainActivity::class.java)) finish() } } + + private fun resetAllLayouts() { + findViewById(R.id.male_layout).setBackgroundResource(R.drawable.gender_background) + findViewById(R.id.female_layout).setBackgroundResource(R.drawable.gender_background) + findViewById(R.id.third_layout).setBackgroundResource(R.drawable.gender_background) + } } diff --git a/app/src/main/java/com/example/myapplication/SplashActivity.kt b/app/src/main/java/com/example/myapplication/SplashActivity.kt index ebc24d2..b23f6c9 100644 --- a/app/src/main/java/com/example/myapplication/SplashActivity.kt +++ b/app/src/main/java/com/example/myapplication/SplashActivity.kt @@ -2,23 +2,54 @@ package com.example.myapplication import android.content.Intent import android.os.Bundle +import android.os.Handler +import android.os.Looper +// import android.widget.ProgressBar import androidx.appcompat.app.AppCompatActivity +import com.example.myapplication.network.BehaviorReporter class SplashActivity : AppCompatActivity() { + // private lateinit var progressBar: ProgressBar + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + setContentView(R.layout.activity_splash) - val prefs = getSharedPreferences("app_prefs", MODE_PRIVATE) - val isFirstLaunch = prefs.getBoolean("is_first_launch", true) - if (isFirstLaunch) { - // 第一次启动 → 进入引导页 - startActivity(Intent(this, OnboardingActivity::class.java)) - } else { - // 不是第一次 → 直接进入主界面 - startActivity(Intent(this, MainActivity::class.java)) - } - finish() + // progressBar = findViewById(R.id.progressBar) + + Handler(Looper.getMainLooper()).postDelayed({ + val prefs = getSharedPreferences("app_prefs", MODE_PRIVATE) + val isFirstLaunch = prefs.getBoolean("is_first_launch", true) + if(isFirstLaunch){ + BehaviorReporter.report( + isNewUser = false, + "page_id" to "sex_select", + ) + BehaviorReporter.report( + isNewUser = false, + "page_id" to "guide", + ) + } + + val targetIntent = if (isFirstLaunch) { + // 第一次启动 → 进入引导页 + prefs.edit().putBoolean("is_first_launch", false).apply() + Intent(this, OnboardingActivity::class.java) + } else { + // 不是第一次 → 进入主界面,携带原始intent的参数 + Intent(this, MainActivity::class.java).apply { + intent.extras?.let { putExtras(it) } + } + } + startActivity(targetIntent) + finish() + }, 1000) // 0.5秒延迟,确保初始化 + } + + override fun onDestroy() { + // progressBar.clearAnimation() + super.onDestroy() } } diff --git a/app/src/main/java/com/example/myapplication/keyboard/AiKeyboard.kt b/app/src/main/java/com/example/myapplication/keyboard/AiKeyboard.kt index fa80317..8430982 100644 --- a/app/src/main/java/com/example/myapplication/keyboard/AiKeyboard.kt +++ b/app/src/main/java/com/example/myapplication/keyboard/AiKeyboard.kt @@ -1,24 +1,46 @@ package com.example.myapplication.keyboard +import android.content.Context import android.content.Intent import android.content.res.ColorStateList import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Typeface import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable +import android.os.Handler +import android.os.Looper import android.util.TypedValue import android.view.Gravity import android.view.View +import android.view.ViewGroup import android.widget.ImageView import android.widget.LinearLayout -import android.widget.TextView -import com.example.myapplication.MainActivity -import com.example.myapplication.theme.ThemeManager -import android.os.Handler -import android.os.Looper import android.widget.ScrollView -import com.example.myapplication.network.NetworkClient +import android.widget.TextView +import android.content.ClipboardManager +import android.util.Log +import android.widget.Toast +import android.view.inputmethod.ExtractedTextRequest + +import com.example.myapplication.SplashActivity import com.example.myapplication.network.LlmStreamCallback +import com.example.myapplication.network.ListByUserWithNot +import com.example.myapplication.network.ApiResponse +import com.example.myapplication.network.NetworkClient +import com.example.myapplication.network.RetrofitClient +import com.example.myapplication.theme.ThemeManager +import com.google.android.flexbox.FlexboxLayout + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import okhttp3.Call +import kotlin.math.min +import com.example.myapplication.network.BehaviorReporter class AiKeyboard( env: KeyboardEnvironment @@ -26,7 +48,18 @@ class AiKeyboard( private var currentStreamCall: Call? = null private val mainHandler = Handler(Looper.getMainLooper()) + private val uiScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) + // ✅ 卡片选中的 characterId(SSE 参数) + private var selectedCharacterId: Int? = null + + // ✅ Paste 得到的内容(SSE message) + private var lastPastedText: String? = null + + // ✅ 记录“上次从卡片填入到输入框”的文本,用于覆盖 + private var lastFilledText: String? = null + + // 输出容器 private val messagesContainer: LinearLayout by lazy { val res = env.ctx.resources val id = res.getIdentifier("container_messages", "id", env.ctx.packageName) @@ -39,15 +72,13 @@ class AiKeyboard( rootView.findViewById(id) } - // 当前正在流式更新的那一个 AI 文本 private var currentAssistantTextView: TextView? = null - - // 用来处理 的缓冲 private val streamBuffer = StringBuilder() - - //新建一条 AI 消息(空内容),返回里面的 TextView 用来后续流式更新 - + /** + * ✅ inflate 一条消息(item_ai_message.xml) + * ✅ 点击卡片:把内容填入输入框,并覆盖上次填入内容 + */ private fun addAssistantMessage(initialText: String = ""): TextView { val inflater = env.layoutInflater val res = env.ctx.resources @@ -58,160 +89,440 @@ class AiKeyboard( res.getIdentifier("tv_content", "id", env.ctx.packageName) ) tv.text = initialText - messagesContainer.addView(itemView) + // ✅ 点击整张卡片:把当前卡片内容填入输入框,并覆盖上次填入内容 + itemView.setOnClickListener { + val text = tv.text?.toString().orEmpty() + if (text.isNotBlank()) { + fillToEditorOverwriteLast(text) + BehaviorReporter.report( + isNewUser = false, + "page_id" to "keyboard_StreamTextView", + "handle_label" to "handle_label", + "send_text" to text, + ) + } + } + + messagesContainer.addView(itemView) scrollToBottom() return tv } - /** - * (可选)如果你也想显示用户提问 - */ - private fun addUserMessage(text: String) { - // 简单写:复用同一个 item 布局 - val tv = addAssistantMessage(text) - // 这里可以改成设置 gravity、背景区分用户/AI 等 - } - private fun scrollToBottom() { - // 延迟一点点执行,保证 addView 完成后再滚动 messagesScrollView.post { messagesScrollView.fullScroll(View.FOCUS_DOWN) } } - //后端每来一个 llm_chunk 的 data,就调用一次这个方法 private fun onLlmChunk(data: String) { - // 丢掉 data=":\n\n" 这条 if (data == ":\n\n") return - // 确保在主线程更新 UI mainHandler.post { - // 如果还没有正在流式的 TextView,就新建一条 AI 消息 if (currentAssistantTextView == null) { currentAssistantTextView = addAssistantMessage("") streamBuffer.clear() } - // 累积到缓冲区 streamBuffer.append(data) - - // 先整体把 ":\n\n" 删掉(以防万一有别的地方混进来) var text = streamBuffer.toString().replace(":\n\n", "") - // 处理 :代表下一句/下一条消息 val splitTag = "" var index = text.indexOf(splitTag) while (index != -1) { - // split 前面这一段是上一条消息的最终内容 val before = text.substring(0, index) currentAssistantTextView?.text = before scrollToBottom() - // 开启下一条 AI 消息 currentAssistantTextView = addAssistantMessage("") - - // 剩下的留给下一轮 text = text.substring(index + splitTag.length) index = text.indexOf(splitTag) } - // 循环结束后 text 就是「当前这条消息的未完成尾巴」 currentAssistantTextView?.text = text scrollToBottom() - // 缓冲区只保留尾巴(避免无限变长) streamBuffer.clear() streamBuffer.append(text) } } - - // 收到 type="done" 时调用,表示这一轮回答结束 private fun onLlmDone() { mainHandler.post { - // 这里目前不需要做太多事,必要的话可以清掉 buffer streamBuffer.clear() currentAssistantTextView = null } } - - // 开始一次新的 AI 回答流式请求 - fun startAiStream(userQuestion: String) { - // 可选:先把用户问题显示出来 - addUserMessage(userQuestion) + // ✅ 关键:调用 POST /chat/talk 的 SSE,并渲染到输出区 + private fun startTalkStream(characterId: Int, message: String) { + // 每次发起新对话:清空输出区(你如果想保留历史,把这行删掉) + messagesContainer.removeAllViews() - // 如果之前有没结束的流,先取消 + // 取消旧流 currentStreamCall?.cancel() - currentStreamCall = NetworkClient.startLlmStream( - question = userQuestion, + currentStreamCall = NetworkClient.startChatTalkStream( + characterId = characterId, + message = message, callback = object : LlmStreamCallback { override fun onEvent(type: String, data: String?) { - when (type) { - "llm_chunk" -> { - if (data != null) { - onLlmChunk(data) // 这里就是之前写的流式 UI 更新 + guard("SSE.onEvent(type=$type)") { + when (type) { + "llm_chunk" -> if (data != null) onLlmChunk(data) + "done" -> onLlmDone() + else -> { + Log.d("AI_KB", "unknown event type=$type data=${data?.take(200)}") } } - "done" -> { - onLlmDone() // 一轮结束 - } - "search_result" -> { - } } } override fun onError(t: Throwable) { - addAssistantMessage("出错了:${t.message}") + // 尝试解析JSON错误响应 + val errorResponse = try { + val errorJson = t.message?.let { + org.json.JSONObject(it) + } + if (errorJson != null) { + ApiResponse( + code = errorJson.optInt("code", 500), + message = errorJson.optString("message", "Unknown error"), + data = null + ) + } else { + ApiResponse( + code = 500, + message = t.message ?: "Unknown error", + data = null + ) + } + } catch (e: Exception) { + ApiResponse( + code = 500, + message = t.message ?: "Unknown error", + data = null + ) + } + + // SSE错误处理(如没有vip) + if (errorResponse.code == 50022) { + mainHandler.post { + Toast.makeText(env.ctx, errorResponse.message, Toast.LENGTH_LONG).show() + } + } else { + showErrorOnUi(errorResponse.message) + } } } ) } - // 比如键盘关闭时可以调用一次,避免内存泄漏 / 多余请求 fun cancelAiStream() { currentStreamCall?.cancel() currentStreamCall = null } - - - // 以下是 BaseKeyboard 的实现 override val rootView: View = run { val res = env.ctx.resources val layoutId = res.getIdentifier("ai_keyboard", "layout", env.ctx.packageName) if (layoutId != 0) { env.layoutInflater.inflate(layoutId, null) as View } else { - // 如果找不到布局,创建一个默认的View LinearLayout(env.ctx).apply { orientation = LinearLayout.VERTICAL gravity = Gravity.CENTER - addView(TextView(env.ctx).apply { - text = "AI Keyboard" - }) + addView(TextView(env.ctx).apply { text = "AI Keyboard" }) } } } init { - applyKeyBackground(rootView, "background") - applyTheme( - env.currentTextColor, - env.currentBorderColor, - env.currentBackgroundColor - ) - setupListeners() + guard("AiKeyboard.init") { + applyKeyBackground(rootView, "background") + applyTheme(env.currentTextColor, env.currentBorderColor, env.currentBackgroundColor) + setupListeners() + loadPersonasAndRender() + } } - - private fun applyKeyBackground( - root: View, - viewIdName: String, - drawableName: String? = null - ) { + + // ========= UI 绑定 ========= + + private fun setupListeners() { + val res = env.ctx.resources + val pkg = env.ctx.packageName + + val aiPersonaId = res.getIdentifier("ai_persona", "id", pkg) + val aiOutputId = res.getIdentifier("ai_output", "id", pkg) + val aiPersonaView = if (aiPersonaId != 0) rootView.findViewById(aiPersonaId) else null + val aiOutputView = if (aiOutputId != 0) rootView.findViewById(aiOutputId) else null + + aiPersonaView?.visibility = View.VISIBLE + aiOutputView?.visibility = View.GONE + + // 返回主键盘 + val backId = res.getIdentifier("key_abc", "id", pkg) + if (backId != 0) { + rootView.findViewById(backId)?.setOnClickListener { + env.showMainKeyboard() + BehaviorReporter.report( + isNewUser = false, + "page_id" to "keyboard_main_panel", + ) + } + } + + // VIP + val vipButtonId = res.getIdentifier("key_vip", "id", pkg) + if (vipButtonId != 0) { + rootView.findViewById(vipButtonId)?.setOnClickListener { + navigateToRechargeFragment() + BehaviorReporter.report( + isNewUser = false, + "page_id" to "keyboard_subscription_panel", + ) + } + } + + // Return:输出区 -> 人设区 + val returnButtonId = res.getIdentifier("Return_keyboard", "id", pkg) + if (returnButtonId != 0) { + rootView.findViewById(returnButtonId)?.setOnClickListener { + aiOutputView?.animate()?.alpha(0f)?.setDuration(150)?.withEndAction { + aiOutputView.visibility = View.GONE + aiPersonaView?.visibility = View.VISIBLE + aiPersonaView?.alpha = 0f + aiPersonaView?.animate()?.alpha(1f)?.setDuration(150) + } + } + } + + // Paste:读取剪贴板第一条 -> 保存 lastPastedText,并显示到 completion_text + val pasteBtnId = res.getIdentifier("keyboard_button_Paste", "id", pkg) + val completionTextId = res.getIdentifier("completion_text", "id", pkg) + if (pasteBtnId != 0) { + rootView.findViewById(pasteBtnId)?.setOnClickListener { + val text = readClipboardFirstText() + lastPastedText = text + + if (completionTextId != 0) { + rootView.findViewById(completionTextId)?.text = + if (text.isNullOrBlank()) "" else text + } + } + } + + // HorizontalScrollView:点击事件与Paste按钮相同 + val scrollViewId = res.getIdentifier("completion_container", "id", pkg) + if (scrollViewId != 0) { + rootView.findViewById(scrollViewId)?.setOnClickListener { + val text = readClipboardFirstText() + lastPastedText = text + + if (completionTextId != 0) { + rootView.findViewById(completionTextId)?.text = + if (text.isNullOrBlank()) "" else text + } + } + } + + // ✅ Delete:沿用 MyInputMethodService.deleteOne() + val deleteBtnId = res.getIdentifier("keyboard_button_Delete", "id", pkg) + if (deleteBtnId != 0) { + rootView.findViewById(deleteBtnId)?.setOnClickListener { + env.deleteOne() // 就是 MyInputMethodService.deleteOne() + lastFilledText = null // 可选:防止覆盖逻辑误删 + } + } + + // ✅ Send:沿用 MyInputMethodService.performSendAction() + val sendBtnId = res.getIdentifier("keyboard_button_Send", "id", pkg) + if (sendBtnId != 0) { + rootView.findViewById(sendBtnId)?.setOnClickListener { + env.performSendAction() // 就是 MyInputMethodService.performSendAction() + lastFilledText = null // 发送后不再尝试覆盖 + } + } + + // ✅ Clear:清空输入框 + val clearBtnId = res.getIdentifier("keyboard_button_Clear", "id", pkg) + if (clearBtnId != 0) { + rootView.findViewById(clearBtnId)?.setOnClickListener { + clearEditorAll() + lastFilledText = null + } + } + } + + // ========= 人设卡片:listByUser 渲染 ========= + + fun refreshPersonas() { + loadPersonasAndRender() + } + + private fun loadPersonasAndRender() { + val res = env.ctx.resources + val pkg = env.ctx.packageName + val containerId = res.getIdentifier("persona_container", "id", pkg) + val container = if (containerId != 0) rootView.findViewById(containerId) else null + if (container == null) return + + uiScope.launch { + try { + val resp = withContext(Dispatchers.IO) { RetrofitClient.apiService.listByUser() } + + Log.d("1314520-AI_KB", "listByUser response: $resp") + if (resp.code == 0) { + val list = resp.data ?: emptyList() + renderPersonaCards(container, list) + } else if (resp.code == 40102) { + Toast.makeText(env.ctx, "You need to log in to use this function.", Toast.LENGTH_LONG).show() + } else { + Toast.makeText(env.ctx, resp.message, Toast.LENGTH_LONG).show() + } + } catch (_: Throwable) { + container.removeAllViews() + } + } + } + + private fun renderPersonaCards(container: FlexboxLayout, list: List) { + container.removeAllViews() + + val inflater = env.layoutInflater + + list.forEach { item -> + val v = inflater.inflate( + com.example.myapplication.R.layout.item_ai_persona_card, + container, + false + ) + + val avatar = v.findViewById( + com.example.myapplication.R.id.avatar + ) + val nameTv = v.findViewById(com.example.myapplication.R.id.name) + + nameTv.text = item.characterName + + val sizePx = (env.ctx.resources.displayMetrics.density * 20f).toInt() + avatar.setImageBitmap(emojiToBitmap(item.emoji, sizePx)) + + v.setOnClickListener { + + BehaviorReporter.report( + isNewUser = false, + "page_id" to "keyboard_function_panel", + "element_id" to "renshe_item", + "id" to item.characterId, + "name" to item.characterName, + ) + // ✅ SSE 要的是 characterId,不是 id + selectedCharacterId = item.characterId + + val res = env.ctx.resources + val pkg = env.ctx.packageName + val aiPersonaId = res.getIdentifier("ai_persona", "id", pkg) + val aiOutputId = res.getIdentifier("ai_output", "id", pkg) + val aiPersonaView = if (aiPersonaId != 0) rootView.findViewById(aiPersonaId) else null + val aiOutputView = if (aiOutputId != 0) rootView.findViewById(aiOutputId) else null + + // 获取消息内容:优先使用lastPastedText,其次读取剪贴板 + val message = lastPastedText ?: readClipboardFirstText() ?: run { + addAssistantMessage("Please Paste the content of the clipboard first or make sure the clipboard is not empty") + return@setOnClickListener + } + + aiPersonaView?.animate()?.alpha(0f)?.setDuration(150)?.withEndAction { + aiPersonaView.visibility = View.GONE + aiOutputView?.visibility = View.VISIBLE + aiOutputView?.alpha = 0f + aiOutputView?.animate()?.alpha(1f)?.setDuration(150) + } + + // 发送SSE请求 + startTalkStream(item.characterId, message) + } + + container.addView(v) + } + } + + private fun emojiToBitmap(emoji: String, sizePx: Int): Bitmap { + val safeSize = min(sizePx.coerceAtLeast(16), 128) + val bmp = Bitmap.createBitmap(safeSize, safeSize, Bitmap.Config.ARGB_8888) + val canvas = Canvas(bmp) + + val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply { + textAlign = Paint.Align.CENTER + typeface = Typeface.DEFAULT + textSize = safeSize * 0.8f + } + val fm = paint.fontMetrics + val x = safeSize / 2f + val y = safeSize / 2f - (fm.ascent + fm.descent) / 2f + + canvas.drawText(emoji, x, y, paint) + return bmp + } + + private fun readClipboardFirstText(): String? { + val cm = env.ctx.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager ?: return null + val clip = cm.primaryClip ?: return null + if (clip.itemCount <= 0) return null + return clip.getItemAt(0).coerceToText(env.ctx)?.toString() + } + + /** + * ✅ 将卡片文本填入宿主输入框,并覆盖上一次“卡片填入”的内容 + * 覆盖策略(安全版): + * - 仅当光标前的文本 == lastFilledText 时,才删除那段并覆盖 + * - 否则就直接插入(避免误删用户手动输入的内容) + */ + private fun fillToEditorOverwriteLast(newText: String) { + val ic = env.getInputConnection() ?: return + + ic.beginBatchEdit() + try { + val prev = lastFilledText + if (!prev.isNullOrEmpty()) { + val before = ic.getTextBeforeCursor(prev.length, 0)?.toString() + if (before == prev) { + ic.deleteSurroundingText(prev.length, 0) + } + } + ic.commitText(newText, 1) + lastFilledText = newText + } finally { + ic.endBatchEdit() + } + } + + /** ✅ 清空宿主输入框(当前编辑框) */ + private fun clearEditorAll() { + val ic = env.getInputConnection() ?: return + + val et = try { + ic.getExtractedText(ExtractedTextRequest(), 0) + } catch (_: Throwable) { + null + } + val full = et?.text?.toString().orEmpty() + if (full.isEmpty()) return + + ic.beginBatchEdit() + try { + ic.setSelection(0, full.length) + ic.commitText("", 1) + } finally { + ic.endBatchEdit() + } + } + + // ========= 换肤相关(保留你原有逻辑) ========= + + private fun applyKeyBackground(root: View, viewIdName: String, drawableName: String? = null) { val res = env.ctx.resources val viewId = res.getIdentifier(viewIdName, "id", env.ctx.packageName) if (viewId == 0) return @@ -250,125 +561,51 @@ class AiKeyboard( } } - - private fun setupListeners() { - val res = env.ctx.resources - val pkg = env.ctx.packageName - - // 获取ai_persona和ai_output视图引用 - val aiPersonaId = res.getIdentifier("ai_persona", "id", pkg) - val aiOutputId = res.getIdentifier("ai_output", "id", pkg) - - val aiPersonaView = if (aiPersonaId != 0) rootView.findViewById(aiPersonaId) else null - val aiOutputView = if (aiOutputId != 0) rootView.findViewById(aiOutputId) else null - - // 初始化显示状态:显示ai_persona,隐藏ai_output - aiPersonaView?.visibility = View.VISIBLE - aiOutputView?.visibility = View.GONE - - // 如果 ai_keyboard.xml 里有 “返回主键盘” 的按钮,比如 key_abc,就绑定一下 - val backId = res.getIdentifier("key_abc", "id", pkg) - if (backId != 0) { - rootView.findViewById(backId)?.setOnClickListener { - env.showMainKeyboard() - } - } - - // 绑定 VIP 按钮点击事件,跳转到充值页面 - val vipButtonId = res.getIdentifier("key_vip", "id", pkg) - if (vipButtonId != 0) { - rootView.findViewById(vipButtonId)?.setOnClickListener { - navigateToRechargeFragment() - } - } - - //显示切换 - val returnButtonId = res.getIdentifier("Return_keyboard", "id", pkg) - if (returnButtonId != 0) { - rootView.findViewById(returnButtonId)?.let { returnButton -> - // 确保按钮可点击且可获得焦点,防止事件穿透 - returnButton.isClickable = true - returnButton.isFocusable = true - returnButton.setOnClickListener { - // 点击Return_keyboard:先隐藏ai_output,再显示ai_persona(顺序动画) - aiOutputView?.animate()?.alpha(0f)?.setDuration(150)?.withEndAction { - aiOutputView?.visibility = View.GONE - // 等ai_output完全隐藏后再显示ai_persona - aiPersonaView?.visibility = View.VISIBLE - aiPersonaView?.alpha = 0f - aiPersonaView?.animate()?.alpha(1f)?.setDuration(150) - } - } - } - } - - val cardButtonId = res.getIdentifier("card", "id", pkg) - if (cardButtonId != 0) { - rootView.findViewById(cardButtonId)?.setOnClickListener { - // 点击card:先隐藏ai_persona,再显示ai_output(顺序动画) - aiPersonaView?.animate()?.alpha(0f)?.setDuration(150)?.withEndAction { - aiPersonaView?.visibility = View.GONE - // 等ai_persona完全隐藏后再显示ai_output - aiOutputView?.visibility = View.VISIBLE - aiOutputView?.alpha = 0f - aiOutputView?.animate()?.alpha(1f)?.setDuration(150) - } - } - } - - - - - - // // 假设 ai_keyboard.xml 里有一个发送按钮 key_send - // val sendId = res.getIdentifier("key_send", "id", pkg) - // val inputId = res.getIdentifier("et_prompt", "id", pkg) // 假设这是你的输入框 id - - // if (sendId != 0 && inputId != 0) { - // val inputView = rootView.findViewById(inputId) - - // rootView.findViewById(sendId)?.setOnClickListener { - // val question = inputView?.text?.toString()?.trim().orEmpty() - // if (question.isNotEmpty()) { - // startAiStream(question) - // } - // } - // } - } - private fun navigateToRechargeFragment() { try { - val intent = Intent(env.ctx, MainActivity::class.java).apply { - addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP) - putExtra("navigate_to", "recharge_fragment") - } + val intent = Intent(env.ctx, SplashActivity::class.java) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP) + intent.putExtra("navigate_to", "recharge_fragment") env.ctx.startActivity(intent) } catch (e: Exception) { - // 如果启动失败,记录错误日志 - android.util.Log.e("AiKeyboard", "Failed to navigate to recharge fragment", e) + Log.e("AiKeyboard", "Failed to navigate to recharge fragment", e) } } - override fun applyTheme( - textColor: ColorStateList, - borderColor: ColorStateList, - backgroundColor: ColorStateList - ) { + override fun applyTheme(textColor: ColorStateList, borderColor: ColorStateList, backgroundColor: ColorStateList) { applyKeyBackgroundsForTheme() } - // ==============================刷新主题================================== + override fun applyKeyBackgroundsForTheme() { - // 背景 applyKeyBackground(rootView, "background") - - // // AI 键盘上的功能键(按你现有 layout 里出现过的 id 来列) - // val others = listOf( - // "key_abc", // 返回主键盘 - // "key_vip", // VIP - // "Return_keyboard", // 返回 persona 页 - // "card" // 切换到 output 页 - // // 如果后续 ai_keyboard.xml 里还有其它需要换肤的 key id,继续往这里加 - // ) - // others.forEach { applyKeyBackground(rootView, it) } } -} \ No newline at end of file + + private fun showErrorOnUi(title: String, t: Throwable? = null) { + val msg = if (t == null) title else "$title:${t.message ?: t.toString()}" + Log.e("AI_KB", msg, t) + + mainHandler.post { + runCatching { + val res = env.ctx.resources + val pkg = env.ctx.packageName + val aiPersonaId = res.getIdentifier("ai_persona", "id", pkg) + val aiOutputId = res.getIdentifier("ai_output", "id", pkg) + val aiPersonaView = if (aiPersonaId != 0) rootView.findViewById(aiPersonaId) else null + val aiOutputView = if (aiOutputId != 0) rootView.findViewById(aiOutputId) else null + aiPersonaView?.visibility = View.GONE + aiOutputView?.visibility = View.VISIBLE + + addAssistantMessage(msg) + } + } + } + + /** 包一层 try-catch,避免任何地方异常直接白屏 */ + private inline fun guard(tag: String, block: () -> Unit) { + try { + block() + } catch (t: Throwable) { + showErrorOnUi("发生异常($tag)", t) + } + } +} diff --git a/app/src/main/java/com/example/myapplication/keyboard/BaseKeyboard.kt b/app/src/main/java/com/example/myapplication/keyboard/BaseKeyboard.kt index 174ba82..2ff5c5c 100644 --- a/app/src/main/java/com/example/myapplication/keyboard/BaseKeyboard.kt +++ b/app/src/main/java/com/example/myapplication/keyboard/BaseKeyboard.kt @@ -65,8 +65,8 @@ abstract class BaseKeyboard( protected fun applyBorderToAllKeyViews(root: View?) { if (root == null) return - val keyMarginPx = 1.dpToPx() - val keyPaddingH = 6.dpToPx() + val keyMarginPx = 2.dpToPx() + val keyPaddingH = 8.dpToPx() // 忽略 suggestion_0..20(联想栏) val ignoredIds = HashSet().apply { @@ -112,6 +112,12 @@ abstract class BaseKeyboard( return (this * density + 0.5f).toInt() } + /** dp -> px (float version) */ + protected fun Float.dpToPx(): Int { + val density = env.ctx.resources.displayMetrics.density + return (this * density + 0.5f).toInt() + } + /** 按键震动 */ protected fun vibrateKey() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { diff --git a/app/src/main/java/com/example/myapplication/keyboard/KeyboardEnvironment.kt b/app/src/main/java/com/example/myapplication/keyboard/KeyboardEnvironment.kt index cd6f98c..df64ef9 100644 --- a/app/src/main/java/com/example/myapplication/keyboard/KeyboardEnvironment.kt +++ b/app/src/main/java/com/example/myapplication/keyboard/KeyboardEnvironment.kt @@ -39,6 +39,8 @@ interface KeyboardEnvironment { fun showAiKeyboard() //emoji键盘 fun showEmojiKeyboard() + // 关闭联想 + fun associateClose() // 音效 fun playKeyClick() diff --git a/app/src/main/java/com/example/myapplication/keyboard/MainKeyboard.kt b/app/src/main/java/com/example/myapplication/keyboard/MainKeyboard.kt index 74d2be0..e129254 100644 --- a/app/src/main/java/com/example/myapplication/keyboard/MainKeyboard.kt +++ b/app/src/main/java/com/example/myapplication/keyboard/MainKeyboard.kt @@ -15,7 +15,9 @@ import android.view.MotionEvent import android.view.View import android.widget.PopupWindow import android.widget.TextView +import android.widget.LinearLayout import com.example.myapplication.theme.ThemeManager +import com.example.myapplication.network.BehaviorReporter class MainKeyboard( env: KeyboardEnvironment, @@ -145,6 +147,10 @@ class MainKeyboard( view.findViewById(res.getIdentifier("key_ai", "id", pkg))?.setOnClickListener { vibrateKey(); env.showAiKeyboard() + BehaviorReporter.report( + isNewUser = false, + "page_id" to "keyboard_function_panel", + ) } view.findViewById(res.getIdentifier("key_send", "id", pkg))?.setOnClickListener { @@ -159,6 +165,10 @@ class MainKeyboard( updateRevokeButtonVisibility(view, res, pkg) } + view.findViewById(res.getIdentifier("associate_close", "id", pkg))?.setOnClickListener { + vibrateKey();env.associateClose() + } + view.findViewById(res.getIdentifier("key_emoji", "id", pkg))?.setOnClickListener { vibrateKey(); env.showEmojiKeyboard() } diff --git a/app/src/main/java/com/example/myapplication/network/ApiService.kt b/app/src/main/java/com/example/myapplication/network/ApiService.kt index 03895e1..34e67a9 100644 --- a/app/src/main/java/com/example/myapplication/network/ApiService.kt +++ b/app/src/main/java/com/example/myapplication/network/ApiService.kt @@ -1,6 +1,7 @@ // 请求方法 package com.example.myapplication.network +import okhttp3.MultipartBody import okhttp3.ResponseBody import retrofit2.Response import retrofit2.http.* @@ -65,9 +66,42 @@ interface ApiService { @POST("user/updateInfo") suspend fun updateUserInfo( @Body body: updateInfoRequest - ): ApiResponse + ): ApiResponse + + //分页查询钱包交易记录 + @POST("wallet/transactions") + suspend fun transactions( + @Body body: transactionsRequest + ): ApiResponse + //用户人设列表 + @GET("character/listByUser") + suspend fun listByUser( + ): ApiResponse> + + //更新用户人设排序 + @POST("character/updateUserCharacterSort") + suspend fun updateUserCharacterSort( + @Body body: updateUserCharacterSortRequest + ): ApiResponse + + // 删除用户人设 + @GET("character/delUserCharacter") + suspend fun delUserCharacter( + @Query("id") id: Int + ): ApiResponse + + //提交反馈 + @POST("user/feedback") + suspend fun feedback( + @Body body: feedbackRequest + ): ApiResponse + + //查询邀请码 + @GET("user/inviteCode") + suspend fun inviteCode( + ): ApiResponse //===========================================首页================================= // 标签列表 @GET("tag/list") @@ -102,17 +136,11 @@ interface ApiService { @Query("id") id: Int ): ApiResponse - //删除用户人设 - @GET("character/delUserCharacter") - suspend fun delUserCharacter( - @Query("id") id: Int - ): ApiResponse - //添加用户人设 @POST("character/addUserCharacter") suspend fun addUserCharacter( @Body body: AddPersonaClick - ): ApiResponse + ): ApiResponse //==========================================商城=========================================== @@ -188,4 +216,18 @@ interface ApiService { suspend fun downloadZipFromUrl( @Url url: String // 完整的下载 URL ): Response + + +} + +/** + * 文件上传服务接口 + */ +interface FileUploadService { + @Multipart + @POST("file/upload") + suspend fun uploadFile( + @Query("file") fileQuery: String, + @Part file: MultipartBody.Part + ): ApiResponse } diff --git a/app/src/main/java/com/example/myapplication/network/AuthEventBus.kt b/app/src/main/java/com/example/myapplication/network/AuthEventBus.kt index d051603..bfadeae 100644 --- a/app/src/main/java/com/example/myapplication/network/AuthEventBus.kt +++ b/app/src/main/java/com/example/myapplication/network/AuthEventBus.kt @@ -6,7 +6,7 @@ import kotlinx.coroutines.flow.SharedFlow object AuthEventBus { - // replay=0:不缓存历史事件;extraBufferCapacity:避免瞬时丢事件 + // replay=1:缓存最近一次事件;extraBufferCapacity=64:增加缓冲区防止瞬时事件丢失 private val _events = MutableSharedFlow( replay = 0, extraBufferCapacity = 1 @@ -21,8 +21,11 @@ object AuthEventBus { sealed class AuthEvent { data class TokenExpired(val message: String? = null) : AuthEvent() + data class CharacterAdded(val personaId: Int, val newAdded: Boolean = false) : AuthEvent() data class GenericError(val message: String) : AuthEvent() object LoginSuccess : AuthEvent() data class Logout(val returnTabTag: String) : AuthEvent() data class OpenGlobalPage(val destinationId: Int, val bundle: Bundle? = null) : AuthEvent() -} + object UserUpdated : AuthEvent() + data class CharacterDeleted(val characterId: Int) : AuthEvent() + } diff --git a/app/src/main/java/com/example/myapplication/network/BehaviorApiService.kt b/app/src/main/java/com/example/myapplication/network/BehaviorApiService.kt new file mode 100644 index 0000000..d5604d7 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/network/BehaviorApiService.kt @@ -0,0 +1,21 @@ +package com.example.myapplication.network + +import com.google.gson.JsonObject +import retrofit2.Response +import retrofit2.http.Body +import retrofit2.http.POST + +interface BehaviorApiService { + + // 新用户 + @POST("newAccount") + suspend fun reportNewUserBehavior( + @Body body: JsonObject + ): Response + + // 老用户 + @POST("genericData") + suspend fun reportGenericUserBehavior( + @Body body: JsonObject + ): Response +} diff --git a/app/src/main/java/com/example/myapplication/network/BehaviorHttpClient.kt b/app/src/main/java/com/example/myapplication/network/BehaviorHttpClient.kt new file mode 100644 index 0000000..1f66c3b --- /dev/null +++ b/app/src/main/java/com/example/myapplication/network/BehaviorHttpClient.kt @@ -0,0 +1,84 @@ +package com.example.myapplication.network + +import android.util.Log +import okhttp3.Interceptor +import okhttp3.OkHttpClient +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import java.util.concurrent.TimeUnit + +object BehaviorHttpClient { + + private const val TAG = "BehaviorHttp" + + // TODO:改成你的行为服务 baseUrl(必须以 / 结尾) + private const val BASE_URL = "http://192.168.2.21:35310/api/" + + /** + * 请求拦截器:打印请求信息 + */ + private val requestInterceptor = Interceptor { chain -> + val request = chain.request() + + val bodyStr = request.body?.let { body -> + val buffer = okio.Buffer() + body.writeTo(buffer) + buffer.readUtf8() + } + + Log.d(TAG, "201314-请求") + Log.d(TAG, "201314-URL: ${request.url}") + Log.d(TAG, "201314-Method: ${request.method}") + if (!bodyStr.isNullOrBlank()) { + Log.d(TAG, "201314-Body: $bodyStr") + } + + chain.proceed(request) + } + + /** + * 响应拦截器:打印响应信息 + * ⚠️ response.body 只能读一次,这里使用 clone() + */ + private val responseInterceptor = Interceptor { chain -> + val response = chain.proceed(chain.request()) + + val responseBody = response.body + val bodyStr = responseBody?.source()?.let { source -> + source.request(Long.MAX_VALUE) + source.buffer.clone().readUtf8() + } + + Log.d(TAG, "201314-响应") + Log.d(TAG, "201314-URL: ${response.request.url}") + Log.d(TAG, "201314-Code: ${response.code}") + if (!bodyStr.isNullOrBlank()) { + Log.d(TAG, "201314-Body: $bodyStr") + } + + response + } + + private val okHttpClient: OkHttpClient by lazy { + OkHttpClient.Builder() + .connectTimeout(10, TimeUnit.SECONDS) + .readTimeout(10, TimeUnit.SECONDS) + .writeTimeout(10, TimeUnit.SECONDS) + .retryOnConnectionFailure(true) + .addInterceptor(requestInterceptor) + .addInterceptor(responseInterceptor) + .build() + } + + private val retrofit: Retrofit by lazy { + Retrofit.Builder() + .baseUrl(BASE_URL) + .client(okHttpClient) + .addConverterFactory(GsonConverterFactory.create()) + .build() + } + + val service: BehaviorApiService by lazy { + retrofit.create(BehaviorApiService::class.java) + } +} diff --git a/app/src/main/java/com/example/myapplication/network/BehaviorReporter.kt b/app/src/main/java/com/example/myapplication/network/BehaviorReporter.kt new file mode 100644 index 0000000..b2c24d2 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/network/BehaviorReporter.kt @@ -0,0 +1,111 @@ +package com.example.myapplication.network + +import android.util.Log +import com.example.myapplication.AppContext +import com.example.myapplication.utils.EncryptedSharedPreferencesUtil +import com.google.gson.Gson +import com.google.gson.JsonArray +import com.google.gson.JsonElement +import com.google.gson.JsonNull +import com.google.gson.JsonObject +import com.google.gson.JsonPrimitive +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch + +object BehaviorReporter { + + private const val TAG = "BehaviorHttp" + private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO) + private val gson = Gson() + + /** + * 你只管调用这个方法即可: + * + * BehaviorReporter.report( + * isNewUser = true, + * "event" to "register_success", + * "page" to "home", + * "key" to "A", + * "count" to 1, + * "obj" to mapOf("a" to 1) + * ) + * + * @param isNewUser true = newAccount,false = genericData + * @param extra 你想上报的任意字段,null/空字符串/空集合会被过滤掉 + */ + fun report( + isNewUser: Boolean, + vararg extra: Pair + ) { + scope.launch { + runCatching { + + val user = EncryptedSharedPreferencesUtil.get( + AppContext.context, + "user", + LoginResponse::class.java + ) + val token = user?.token.orEmpty() + + // ✅ 用 JsonObject,避免 Retrofit 的 Map wildcard 报错 + val body = JsonObject() + + // token:非空才带(放 body 内) + if (token.isNotBlank()) body.addProperty("token", token) + + // extra:你传什么都行,自动过滤无效值,并转成 JsonElement + extra.forEach { (k, v) -> + val element = v.toJsonElementOrNull() ?: return@forEach + body.add(k, element) + } + + // 根据新/老用户走不同接口 + if (isNewUser) { + BehaviorHttpClient.service.reportNewUserBehavior(body) + } else { + BehaviorHttpClient.service.reportGenericUserBehavior(body) + } + }.onFailure { e -> + Log.e(TAG, "201314-report failed: ${e.message}", e) + } + } + } + + /** + * 把 Any? 转 JsonElement,并过滤掉你不想传的“空值” + * 支持:String/Number/Boolean/Char/Map/List/Set/以及其他对象(尽量 gson.toJsonTree) + */ + private fun Any?.toJsonElementOrNull(): JsonElement? { + return when (this) { + null -> null + + is String -> if (this.isBlank()) null else JsonPrimitive(this) + is Number -> JsonPrimitive(this) + is Boolean -> JsonPrimitive(this) + is Char -> JsonPrimitive(this.toString()) + + is Map<*, *> -> { + if (this.isEmpty()) return null + gson.toJsonTree(this).takeIf { it != JsonNull.INSTANCE } + } + + is List<*> -> { + if (this.isEmpty()) return null + gson.toJsonTree(this).takeIf { it != JsonNull.INSTANCE } ?: JsonArray() + } + + is Set<*> -> { + if (this.isEmpty()) return null + gson.toJsonTree(this).takeIf { it != JsonNull.INSTANCE } + } + + else -> { + // 兜底:尽量序列化;失败就丢弃,避免影响主流程 + runCatching { gson.toJsonTree(this) }.getOrNull() + ?.takeIf { it != JsonNull.INSTANCE } + } + } + } +} diff --git a/app/src/main/java/com/example/myapplication/network/HttpInterceptors.kt b/app/src/main/java/com/example/myapplication/network/HttpInterceptors.kt index 8e88907..b3bf2a9 100644 --- a/app/src/main/java/com/example/myapplication/network/HttpInterceptors.kt +++ b/app/src/main/java/com/example/myapplication/network/HttpInterceptors.kt @@ -9,7 +9,16 @@ import okhttp3.Response import okhttp3.ResponseBody.Companion.toResponseBody import com.example.myapplication.utils.EncryptedSharedPreferencesUtil import android.content.Context - +import com.example.myapplication.network.security.BodyParamsExtractor +import com.example.myapplication.network.security.NonceUtils +import com.example.myapplication.network.security.SignUtils +import com.google.gson.JsonElement +import com.google.gson.JsonParser +import okio.Buffer +import java.net.URLDecoder +import java.net.URLEncoder +import javax.crypto.Mac +import javax.crypto.spec.SecretKeySpec // * 不需要登录的接口路径(相对完整路径) // * 只写 /api/ 后面的部分 @@ -18,59 +27,95 @@ import android.content.Context private val NO_LOGIN_REQUIRED_PATHS = setOf( "/themes/listByStyle", "/wallet/balance", + "/character/listByUser", ) -private fun noLoginRequired(url: HttpUrl): Boolean { - val path = url.encodedPath // 例:/api/home/banner +private val NO_SIGN_REQUIRED_PATHS = setOf( + "/auth/login", +) - // 统一裁掉 /api 前缀 - val apiPath = path.substringAfter("/api", path) - - return NO_LOGIN_REQUIRED_PATHS.contains(apiPath) +private fun apiPath(url: HttpUrl): String { + val path = url.encodedPath + return path.substringAfter("/api", path) } +private fun noLoginRequired(url: HttpUrl): Boolean = + NO_LOGIN_REQUIRED_PATHS.contains(apiPath(url)) + +private fun noSignRequired(url: HttpUrl): Boolean = + NO_SIGN_REQUIRED_PATHS.contains(apiPath(url)) + /** * 请求拦截器:统一加 Header、token 等 */ fun requestInterceptor(appContext: Context) = Interceptor { chain -> val original = chain.request() + val url = original.url val user = EncryptedSharedPreferencesUtil.get(appContext, "user", LoginResponse::class.java) val token = user?.token.orEmpty() - val newRequest = original.newBuilder() + val builder = original.newBuilder() .apply { - if (token.isNotBlank()) { - addHeader("auth-token", "$token") - } + if (token.isNotBlank()) addHeader("auth-token", token) } .addHeader("Accept-Language", "lang") - .build() - // ===== 打印请求信息 ===== - val request = newRequest - val url = request.url + // ======= ✅ 按你规则加签名(header + query + body)======= + if (!noSignRequired(url)) { + val appId = "loveKeyboard" + val secret = "kZJM39HYvhxwbJkG1fmquQRVkQiLAh2H" // TODO 正式环境建议下发/混淆/NDK + val timestamp = (System.currentTimeMillis() / 1000).toString() + val nonce = java.util.UUID.randomUUID().toString().replace("-", "").take(16) + + // 1) 合并成 Map(去掉 sign 本身) + val params = linkedMapOf() + params["appId"] = appId + params["timestamp"] = timestamp + params["nonce"] = nonce + + // 2) query 参数 + for (i in 0 until url.querySize) { + params[url.queryParameterName(i)] = url.queryParameterValue(i).orEmpty() + } + + // 3) body 参数(json / form) + params.putAll(extractBodyParams(original)) + + // 4) 生成 sign + val sign = calcSign(params, secret) + + builder + .addHeader("X-App-Id", appId) + .addHeader("X-Timestamp", timestamp) + .addHeader("X-Nonce", nonce) + .addHeader("X-Sign", sign) + } + + val request = builder.build() + + // ===== 打印请求信息(保留你原来的)===== val sb = StringBuilder() sb.append("\n======== HTTP Request ========\n") sb.append("Method: ${request.method}\n") - sb.append("URL: $url\n") + sb.append("URL: ${request.url}\n") sb.append("Headers:\n") for (name in request.headers.names()) { sb.append(" $name: ${request.header(name)}\n") } - if (url.querySize > 0) { + if (request.url.querySize > 0) { sb.append("Query Params:\n") - for (i in 0 until url.querySize) { - sb.append(" ${url.queryParameterName(i)} = ${url.queryParameterValue(i)}\n") + for (i in 0 until request.url.querySize) { + sb.append(" ${request.url.queryParameterName(i)} = ${request.url.queryParameterValue(i)}\n") } } val requestBody = request.body if (requestBody != null) { - val buffer = okio.Buffer() + val buffer = Buffer() requestBody.writeTo(buffer) sb.append("Body:\n") sb.append(buffer.readUtf8()) @@ -83,6 +128,132 @@ fun requestInterceptor(appContext: Context) = Interceptor { chain -> chain.proceed(request) } +// +// ================== 签名工具(严格按你描述规则) ================== +// + +private fun calcSign(params: Map, secret: String): String { + // 去空值 + 去 sign + val filtered = params + .filter { (k, v) -> v.isNotBlank() && !k.equals("sign", ignoreCase = true) } + + // 按 key 字典序排序 + val sorted = filtered.toSortedMap() + + // 拼接:k=v&...&secret=xxx(value 统一做 URL encode 防止 & = 破坏结构) + val sb = StringBuilder() + sorted.forEach { (k, v) -> + if (sb.isNotEmpty()) sb.append("&") + sb.append(k).append("=").append(urlEncode(v)) + } + sb.append("&secret=").append(urlEncode(secret)) + + // HMAC-SHA256 -> hex小写 + return hmacSha256Hex(sb.toString(), secret) +} + +private fun hmacSha256Hex(data: String, secret: String): String { + val mac = Mac.getInstance("HmacSHA256") + val keySpec = SecretKeySpec(secret.toByteArray(Charsets.UTF_8), "HmacSHA256") + mac.init(keySpec) + val bytes = mac.doFinal(data.toByteArray(Charsets.UTF_8)) + return bytes.joinToString("") { "%02x".format(it) } +} + +private fun urlEncode(v: String): String = + URLEncoder.encode(v, "UTF-8") + +// +// ================== Body 参数提取:json / form ================== +// + +private fun extractBodyParams(request: okhttp3.Request): Map { + val body = request.body ?: return emptyMap() + val ct = body.contentType()?.toString()?.lowercase().orEmpty() + + return when { + ct.contains("application/json") -> extractJsonBody(body) + ct.contains("application/x-www-form-urlencoded") -> extractFormBody(body) + else -> emptyMap() // multipart / stream 等默认不签 body(如需可再扩展) + } +} + +private fun extractJsonBody(body: okhttp3.RequestBody): Map { + val raw = bodyToString(body).trim() + if (raw.isBlank()) return emptyMap() + + return try { + val root: JsonElement = JsonParser.parseString(raw) + val out = linkedMapOf() + flattenJson(root, "", out) + out + } catch (_: Exception) { + emptyMap() + } +} + +private fun extractFormBody(body: okhttp3.RequestBody): Map { + val raw = bodyToString(body) + if (raw.isBlank()) return emptyMap() + + val map = linkedMapOf() + raw.split("&") + .filter { it.isNotBlank() } + .forEach { pair -> + val idx = pair.indexOf("=") + if (idx > 0) { + val k = pair.substring(0, idx) + val v = pair.substring(idx + 1) + // form 这里解码回“原值”,后续签名阶段再统一 encode + map[k] = URLDecoder.decode(v, "UTF-8") + } else { + map[pair] = "" + } + } + return map +} + +private fun bodyToString(body: okhttp3.RequestBody): String { + return try { + val buffer = Buffer() + body.writeTo(buffer) + val charset = body.contentType()?.charset(Charsets.UTF_8) ?: Charsets.UTF_8 + buffer.readString(charset) + } catch (_: Exception) { + "" + } +} + +/** + * JSON 扁平化规则: + * object: a.b.c + * array : items[0].id + */ +private fun flattenJson(elem: JsonElement, prefix: String, out: MutableMap) { + when { + elem.isJsonNull -> { + // null 不参与签名(服务端也要一致) + } + elem.isJsonPrimitive -> { + if (prefix.isNotBlank()) out[prefix] = elem.asJsonPrimitive.toString().trim('"') + } + elem.isJsonObject -> { + val obj = elem.asJsonObject + for ((k, v) in obj.entrySet()) { + val newKey = if (prefix.isBlank()) k else "$prefix.$k" + flattenJson(v, newKey, out) + } + } + elem.isJsonArray -> { + val arr = elem.asJsonArray + for (i in 0 until arr.size()) { + val newKey = "$prefix[$i]" + flattenJson(arr[i], newKey, out) + } + } + } +} + /** * 响应拦截器:统一打印日志、做一些简单的错误处理 @@ -121,7 +292,7 @@ val responseInterceptor = Interceptor { chain -> val gson = Gson() val errorResponse = gson.fromJson(bodyString, ErrorResponse::class.java) - if (errorResponse.code == 40102) { + if (errorResponse.code == 40102|| errorResponse.code == 40103) { val isNoLoginApi = noLoginRequired(request.url) Log.w( @@ -134,14 +305,14 @@ val responseInterceptor = Interceptor { chain -> AuthEventBus.emit(AuthEvent.TokenExpired(errorResponse.message)) } - return@Interceptor response.newBuilder() - .code(401) - .message( - if (isNoLoginApi) response.message - else "Login required: ${errorResponse.message}" - ) - .body(bodyString.toResponseBody(mediaType)) - .build() + // return@Interceptor response.newBuilder() + // .code(401) + // .message( + // if (isNoLoginApi) response.message + // else "Login required: ${errorResponse.message}" + // ) + // .body(bodyString.toResponseBody(mediaType)) + // .build() } // 其他非0的错误码,通过事件总线发送错误信息 else if (errorResponse.code!= 0) { diff --git a/app/src/main/java/com/example/myapplication/network/Models.kt b/app/src/main/java/com/example/myapplication/network/Models.kt index 1c2a916..be6e683 100644 --- a/app/src/main/java/com/example/myapplication/network/Models.kt +++ b/app/src/main/java/com/example/myapplication/network/Models.kt @@ -71,18 +71,77 @@ data class User( val email: String, val emailVerified: Boolean, val isVip: Boolean, - val vipExpiry: String, - val token: String + val vipExpiry: String?, + val token: String?, ) //更新用户 data class updateInfoRequest( - val uid: Long, - val nickName: String, - val gender: Int, - val avatarUrl: String?, + val uid: Long? = null, + val nickName: String? = null, + val gender: Int? = null, + val avatarUrl: String? = null, ) +//分页查询钱包交易记录 +data class transactionsRequest( + val pageNum: Int, + val pageSize: Int, +) + +//分页查询钱包交易记录响应 +data class transactionsResponse( + val records: List, + val total: Int, + val size: Int, + val current: Int, + val pages: Int +) + +//分页查询钱包交易记录响应 +data class TransactionRecord( + val id: Long, + val type: Int, + val amount: Number, + val beforeBalance: Number, + val afterBalance: Number, + val description: String, + val createdAt: String, +) + +//用户人设列表响应 +data class ListByUserWithNot( + val id: Int, + val characterName: String, + val emoji: String, + val characterId: Int, +) + +//更新用户人设排序 +data class updateUserCharacterSortRequest( + val sort: List +) + +//提交反馈 +data class feedbackRequest( + val content: String, +) + +//分享响应 +data class ShareResponse( + val code: String, + val status: Int, + val usedCount: Int, + val maxUses: Int, + val expiresAt: String, + val h5Link: String, +) + +data class FreeTrialQuota( + val effectiveQuota: Int, + val nacosQuota: Int, + val source: Int, +) // =======================================首页====================================== //标签列表 data class Tag( @@ -115,7 +174,7 @@ data class listByTagWithNotLogin( // 人设详情响应 data class CharacterDetailResponse( - val id: Long? = null, + val id: Int? = null, val characterName: String? = null, val characterBackground: String? = null, val avatarUrl: String? = null, @@ -189,4 +248,4 @@ data class deleteThemeRequest( //购买主题 data class purchaseThemeRequest( val themeId: Int, -) \ No newline at end of file +) diff --git a/app/src/main/java/com/example/myapplication/network/NetworkClient.kt b/app/src/main/java/com/example/myapplication/network/NetworkClient.kt index 97fae86..d2d25f9 100644 --- a/app/src/main/java/com/example/myapplication/network/NetworkClient.kt +++ b/app/src/main/java/com/example/myapplication/network/NetworkClient.kt @@ -1,116 +1,406 @@ package com.example.myapplication.network +import android.content.Context +import android.util.Log import okhttp3.* +import okio.BufferedSource import okio.Buffer import org.json.JSONObject import java.io.IOException +import java.net.URLDecoder +import java.net.URLEncoder +import java.util.UUID import java.util.concurrent.TimeUnit +import javax.crypto.Mac +import javax.crypto.spec.SecretKeySpec import okhttp3.MediaType.Companion.toMediaType import okhttp3.RequestBody.Companion.toRequestBody +import com.example.myapplication.utils.EncryptedSharedPreferencesUtil object NetworkClient { - // 你自己后端的 base url private const val BASE_URL = "http://192.168.2.21:7529/api" + private const val TAG = "999-SSE_TALK" + + // ====== 按你给的规则固定值 ====== + private const val APP_ID = "loveKeyboard" + private const val SECRET = "kZJM39HYvhxwbJkG1fmquQRVkQiLAh2H" + + @Volatile + private lateinit var appContext: Context + + fun init(context: Context) { + appContext = context.applicationContext + } + + private fun debugLoggingInterceptor(): Interceptor = Interceptor { chain -> + val req = chain.request() + Log.d("999-HTTP", ">>> ${req.method} ${req.url}") + Log.d("999-HTTP", ">>> headers:\n${req.headers}") + val resp = chain.proceed(req) + Log.d("999-HTTP", "<<< code=${resp.code} ct=${resp.header("Content-Type")} len=${resp.header("Content-Length")}") + Log.d("999-HTTP", "<<< headers:\n${resp.headers}") + resp + } - // 专门用于 SSE 的 OkHttpClient:readTimeout = 0 代表不超时,一直保持连接 private val sseClient: OkHttpClient by lazy { + check(::appContext.isInitialized) { + "NetworkClient not initialized. Call NetworkClient.init(context) first." + } + OkHttpClient.Builder() - .readTimeout(0, TimeUnit.MILLISECONDS) // SSE 必须不能有读超时 + .readTimeout(0, TimeUnit.MILLISECONDS) + .addInterceptor(debugLoggingInterceptor()) .build() } - /** - * 启动一次 SSE 流式请求 - * @param question 用户问题(你要传给后端的) - * @return Call,可用于取消(比如用户关闭键盘时) - */ - fun startLlmStream( - question: String, + fun startChatTalkStream( + characterId: Int, + message: String, callback: LlmStreamCallback ): Call { - // 根据你后端的接口改:是 POST 还是 GET,参数格式是什么 + Log.d(TAG, "POST /chat/talk send -> characterId=$characterId, message=${message.take(200)}") + val json = JSONObject().apply { - put("query", question) // 假设你后端字段叫 query + put("characterId", characterId) + put("message", message) } val requestBody = json.toString() .toRequestBody("application/json; charset=utf-8".toMediaType()) - val request = Request.Builder() - .url("$BASE_URL/llm/stream") // TODO: 换成你真实的 SSE 路径 + val baseRequest = Request.Builder() + .url("$BASE_URL/chat/talk") .post(requestBody) - // 有些 SSE 接口会要求 Accept - .addHeader("Accept", "text/event-stream") .build() - val call = sseClient.newCall(request) + // ✅ 在这里:按你提供的规则生成签名并加 header + val signedRequest = signRequest(baseRequest) + + Log.d(TAG, "before newCall") + val call = sseClient.newCall(signedRequest) + Log.d(TAG, "after newCall -> enqueue") call.enqueue(object : Callback { override fun onFailure(call: Call, e: IOException) { - if (call.isCanceled()) return // 被主动取消就不用回调错误了 + if (call.isCanceled()) { + Log.w(TAG, "canceled: $call") + return + } + Log.e(TAG, "onFailure: ${e.javaClass.name}: ${e.message}", e) callback.onError(e) } override fun onResponse(call: Call, response: Response) { + Log.d(TAG, "onResponse -> $response") + Log.d(TAG, "resp headers -> ${response.headers}") + + val body = response.body + val contentType = body?.contentType()?.toString().orEmpty() + val isSse = contentType.contains("text/event-stream", ignoreCase = true) + if (!response.isSuccessful) { - callback.onError(IOException("SSE failed: ${response.code}")) + val err = peekOrReadBody(response) + Log.e(TAG, "HTTP failed: code=${response.code}, contentType=$contentType, body=$err") + callback.onError(IOException(err)) response.close() return } - val body = response.body ?: run { + if (!isSse) { + val text = peekOrReadBody(response) + Log.w(TAG, "Not SSE response: contentType=$contentType, body=$text") + callback.onError(IOException(text)) + response.close() + return + } + + if (body == null) { callback.onError(IOException("Empty body")) return } - // 长连接读取:一行一行读,直到服务器关闭或我们取消 body.use { b -> val source = b.source() try { - while (!source.exhausted() && !call.isCanceled()) { - val line = source.readUtf8Line() ?: break - if (line.isBlank()) { - // SSE 中空行代表一个 event 结束,这里可以忽略 - continue - } - - // 兼容两种格式: - // 1) 标准 SSE: "data: { ... }" - // 2) 服务器直接一行一个 JSON: "{ ... }" - val payload = if (line.startsWith("data:")) { - line.substringAfter("data:").trim() - } else { - line.trim() - } - - // 你日志里是: - // {"type":"llm_chunk","data":"Her"} - // {"type":"done","data":null} - try { - val jsonObj = JSONObject(payload) - val type = jsonObj.optString("type") - val data = - if (jsonObj.has("data") && !jsonObj.isNull("data")) - jsonObj.getString("data") - else - null - - callback.onEvent(type, data) - } catch (e: Exception) { - // 解析失败就忽略这一行(或者你可以打印下日志) - // Log.e("NetworkClient", "Bad SSE line: $payload", e) - } - } + readSseStream(source, call, callback) } catch (ioe: IOException) { if (!call.isCanceled()) { + Log.e(TAG, "read error: ${ioe.message}", ioe) callback.onError(ioe) } + } catch (t: Throwable) { + Log.e(TAG, "unexpected error: ${t.message}", t) + callback.onError(t) } } } }) + Log.d(TAG, "after enqueue") return call } + + /** + * ✅ 按你给的规则生成: + * - timestamp = 秒 + * - nonce = UUID 去 '-' 后取 16 位 + * - params = appId/timestamp/nonce + query + body(flatten) + * - sign = calcSign(params, secret) + * - headers: X-App-Id / X-Timestamp / X-Nonce / X-Sign + * - token: auth-token(如果有) + */ + private fun signRequest(original: Request): Request { + val url = original.url + + // 你原代码里 token 从 EncryptedSharedPreferencesUtil.get(...) 取 + // 这里保持一致:如果你项目里有这工具类就用它;没有就把 token 获取替换成你自己的方式 + val token = try { + val user = EncryptedSharedPreferencesUtil.get(appContext, "user", LoginResponse::class.java) + user?.token.orEmpty() + } catch (_: Throwable) { + "" + } + + val timestamp = (System.currentTimeMillis() / 1000).toString() + val nonce = UUID.randomUUID().toString().replace("-", "").take(16) + + val params = linkedMapOf() + params["appId"] = APP_ID + params["timestamp"] = timestamp + params["nonce"] = nonce + + // query 参数 + for (i in 0 until url.querySize) { + params[url.queryParameterName(i)] = url.queryParameterValue(i).orEmpty() + } + + // body 参数(json / form) + params.putAll(extractBodyParams(original)) + + val sign = calcSign(params, SECRET) + + val builder = original.newBuilder() + .addHeader("Accept-Language", "lang") + .addHeader("X-App-Id", APP_ID) + .addHeader("X-Timestamp", timestamp) + .addHeader("X-Nonce", nonce) + .addHeader("X-Sign", sign) + .apply { + if (token.isNotBlank()) addHeader("auth-token", token) + } + + val request = builder.build() + + // ✅ 打印签名相关(避免泄露:sign/secret别全量打印到线上) + Log.d(TAG, "signed -> X-App-Id=$APP_ID X-Timestamp=$timestamp X-Nonce=$nonce X-Sign=${sign.take(16)}... tokenPresent=${token.isNotBlank()}") + return request + } + + // ==================== 签名计算(严格按你给的规则) ==================== + + private fun calcSign(params: Map, secret: String): String { + val filtered = params + .filter { (k, v) -> v.isNotBlank() && !k.equals("sign", ignoreCase = true) } + + val sorted = filtered.toSortedMap() + + val sb = StringBuilder() + sorted.forEach { (k, v) -> + if (sb.isNotEmpty()) sb.append("&") + sb.append(k).append("=").append(urlEncode(v)) + } + sb.append("&secret=").append(urlEncode(secret)) + + return hmacSha256Hex(sb.toString(), secret) + } + + private fun hmacSha256Hex(data: String, secret: String): String { + val mac = Mac.getInstance("HmacSHA256") + val keySpec = SecretKeySpec(secret.toByteArray(Charsets.UTF_8), "HmacSHA256") + mac.init(keySpec) + val bytes = mac.doFinal(data.toByteArray(Charsets.UTF_8)) + return bytes.joinToString("") { "%02x".format(it) } + } + + private fun urlEncode(v: String): String = + URLEncoder.encode(v, "UTF-8") + + // ==================== body 参数提取:json / form ==================== + + private fun extractBodyParams(request: Request): Map { + val body = request.body ?: return emptyMap() + val ct = body.contentType()?.toString()?.lowercase().orEmpty() + + return when { + ct.contains("application/json") -> extractJsonBody(body) + ct.contains("application/x-www-form-urlencoded") -> extractFormBody(body) + else -> emptyMap() + } + } + + private fun extractJsonBody(body: RequestBody): Map { + val raw = bodyToString(body).trim() + if (raw.isBlank()) return emptyMap() + + return try { + // 这里只支持 JSON object/array 的扁平化;primitive 忽略 + val root = org.json.JSONTokener(raw).nextValue() + val out = linkedMapOf() + flattenJsonAny(root, "", out) + out + } catch (_: Throwable) { + emptyMap() + } + } + + private fun extractFormBody(body: RequestBody): Map { + val raw = bodyToString(body) + if (raw.isBlank()) return emptyMap() + + val map = linkedMapOf() + raw.split("&") + .filter { it.isNotBlank() } + .forEach { pair -> + val idx = pair.indexOf("=") + if (idx > 0) { + val k = pair.substring(0, idx) + val v = pair.substring(idx + 1) + map[k] = URLDecoder.decode(v, "UTF-8") + } else { + map[pair] = "" + } + } + return map + } + + private fun bodyToString(body: RequestBody): String { + return try { + val buffer = Buffer() + body.writeTo(buffer) + val charset = body.contentType()?.charset(Charsets.UTF_8) ?: Charsets.UTF_8 + buffer.readString(charset) + } catch (_: Throwable) { + "" + } + } + + /** + * JSON 扁平化规则(尽量与你给的 gson flatten 保持一致): + * object: a.b.c + * array : items[0].id + */ + private fun flattenJsonAny(any: Any?, prefix: String, out: MutableMap) { + when (any) { + null -> Unit + is JSONObject -> { + val keys = any.keys() + while (keys.hasNext()) { + val k = keys.next() + val newKey = if (prefix.isBlank()) k else "$prefix.$k" + flattenJsonAny(any.opt(k), newKey, out) + } + } + is org.json.JSONArray -> { + for (i in 0 until any.length()) { + val newKey = "$prefix[$i]" + flattenJsonAny(any.opt(i), newKey, out) + } + } + is Boolean, is Int, is Long, is Double, is Float -> { + if (prefix.isNotBlank()) out[prefix] = any.toString() + } + is String -> { + if (prefix.isNotBlank()) out[prefix] = any + } + else -> { + // 其它类型忽略 + } + } + } + + // ==================== SSE 读取:JSON / 纯文本 chunk ==================== + + private fun readSseStream( + source: BufferedSource, + call: Call, + callback: LlmStreamCallback + ) { + var eventName: String? = null + val dataLines = mutableListOf() + + fun dispatch() { + if (eventName == null && dataLines.isEmpty()) return + + val rawData = dataLines.joinToString("\n") + Log.d("999-SSE_TALK-event", "event=${eventName ?: "(null)"} rawData=[${rawData.take(500)}]") + + if (rawData.isNotEmpty()) { + handlePayload(eventName, rawData, callback) + } + + eventName = null + dataLines.clear() + } + + while (!source.exhausted() && !call.isCanceled()) { + val line = source.readUtf8Line() ?: break + Log.d("999-SSE_TALK-raw", "raw: [$line]") + + if (line.isEmpty()) { + dispatch() + continue + } + if (line.startsWith(":")) continue + + val idx = line.indexOf(':') + val field = if (idx == -1) line else line.substring(0, idx) + + val rawValue = if (idx == -1) "" else line.substring(idx + 1) + val value = if (rawValue.startsWith(" ")) rawValue.substring(1) else rawValue + + when (field) { + "event" -> eventName = value + "data" -> dataLines.add(value) + else -> Unit + } + } + + dispatch() + } + + private fun handlePayload(eventName: String?, rawData: String, callback: LlmStreamCallback) { + val trimmed = rawData.trim() + val looksLikeJson = trimmed.startsWith("{") && trimmed.endsWith("}") + + if (looksLikeJson) { + try { + val obj = JSONObject(trimmed) + val type = obj.optString("type", "") + val dataValue = if (obj.has("data") && !obj.isNull("data")) obj.opt("data") else null + val dataStr = dataValue?.toString() + + if (type.isNotBlank()) { + callback.onEvent(type, dataStr) + return + } + } catch (_: Throwable) { + // fallthrough to text + } + } + + callback.onEvent(eventName ?: "text_chunk", rawData) + } + + private fun peekOrReadBody(response: Response): String { + return try { + response.peekBody(1024 * 1024).string() + } catch (_: Throwable) { + try { + response.body?.string().orEmpty() + } catch (t2: Throwable) { + "read body failed: ${t2.message}" + } + } + } } diff --git a/app/src/main/java/com/example/myapplication/network/RetrofitClient.kt b/app/src/main/java/com/example/myapplication/network/RetrofitClient.kt index dc599bd..8812eac 100644 --- a/app/src/main/java/com/example/myapplication/network/RetrofitClient.kt +++ b/app/src/main/java/com/example/myapplication/network/RetrofitClient.kt @@ -6,6 +6,7 @@ import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory import java.util.concurrent.TimeUnit +import com.example.myapplication.network.FileUploadService object RetrofitClient { @@ -50,6 +51,13 @@ object RetrofitClient { retrofit.create(ApiService::class.java) } + /** + * 创建文件上传服务 + */ + fun createFileUploadService(): FileUploadService { + return retrofit.create(FileUploadService::class.java) + } + /** * 创建支持完整 URL 下载的 Retrofit 实例 * @param baseUrl 完整的下载 URL diff --git a/app/src/main/java/com/example/myapplication/network/security/BodyParamsExtractor.kt b/app/src/main/java/com/example/myapplication/network/security/BodyParamsExtractor.kt new file mode 100644 index 0000000..0f47d75 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/network/security/BodyParamsExtractor.kt @@ -0,0 +1,137 @@ +package com.example.myapplication.network.security + +import com.google.gson.JsonElement +import com.google.gson.JsonParser +import okhttp3.MultipartBody +import okhttp3.Request +import okhttp3.RequestBody +import okio.Buffer +import java.nio.charset.Charset + +object BodyParamsExtractor { + + /** + * 抽取 Request 的 body 参数到 map + * - JSON: 扁平化(a.b[0].c) + * - Form: 直接 key=value + * - Multipart: 只签文本字段(文件字段跳过) + */ + fun extractBodyParams(request: Request): Map { + val body = request.body ?: return emptyMap() + val contentType = body.contentType()?.toString()?.lowercase().orEmpty() + + return when { + contentType.contains("application/json") -> extractJsonBody(body) + contentType.contains("application/x-www-form-urlencoded") -> extractFormBody(body) + contentType.contains("multipart/form-data") -> extractMultipartBody(body) + else -> { + // 其他类型(例如 stream、protobuf、octet-stream) + // 建议不签或签一个摘要(需要服务端同样实现) + emptyMap() + } + } + } + + private fun extractJsonBody(body: RequestBody): Map { + val json = bodyToString(body).trim() + if (json.isBlank()) return emptyMap() + + return try { + val root: JsonElement = JsonParser.parseString(json) + val out = linkedMapOf() + flattenJson(root, "", out) + out + } catch (_: Exception) { + // JSON 解析失败就不签 body(也可以选择直接把原文作为 bodyRaw 参与签名) + emptyMap() + } + } + + private fun extractFormBody(body: RequestBody): Map { + // x-www-form-urlencoded 本质就是 querystring:a=1&b=2 + val raw = bodyToString(body) + if (raw.isBlank()) return emptyMap() + + val map = linkedMapOf() + raw.split("&") + .filter { it.isNotBlank() } + .forEach { pair -> + val idx = pair.indexOf("=") + if (idx > 0) { + val k = pair.substring(0, idx) + val v = pair.substring(idx + 1) + // 注意:这里 raw 是已经 urlencoded 的内容 + // 为了与服务端一致,推荐:服务端拿到 form 参数的“解码后值”再参与签名 + // 客户端这里可以不 decode,改为后续签名阶段统一 encode(SignUtils 做了 encode) + map[k] = java.net.URLDecoder.decode(v, "UTF-8") + } else { + map[pair] = "" + } + } + return map + } + + private fun extractMultipartBody(body: RequestBody): Map { + if (body !is MultipartBody) return emptyMap() + + val map = linkedMapOf() + for (i in 0 until body.parts.size) { + val part = body.parts[i] + val headers = part.headers + val disp = headers?.get("Content-Disposition").orEmpty() + + // 取 name="xxx" + val name = Regex("""name="([^"]+)"""").find(disp)?.groupValues?.getOrNull(1) ?: continue + val filename = Regex("""filename="([^"]+)"""").find(disp)?.groupValues?.getOrNull(1) + + // 有 filename 认为是文件字段:默认不签(避免读文件流/超大) + if (!filename.isNullOrBlank()) continue + + // 文本字段:读出内容 + val value = bodyToString(part.body).trim() + map[name] = value + } + return map + } + + private fun bodyToString(body: RequestBody): String { + return try { + val buffer = Buffer() + body.writeTo(buffer) + val charset: Charset = body.contentType()?.charset(Charsets.UTF_8) ?: Charsets.UTF_8 + buffer.readString(charset) + } catch (_: Exception) { + "" + } + } + + /** + * JSON 扁平化规则: + * object: a.b.c + * array: items[0].id + */ + private fun flattenJson(elem: JsonElement, prefix: String, out: MutableMap) { + when { + elem.isJsonNull -> { + // null 不参与(也可以 out[prefix] = "null" 但需要服务端一致) + } + elem.isJsonPrimitive -> { + if (prefix.isNotBlank()) out[prefix] = elem.asJsonPrimitive.toString().trim('"') + } + elem.isJsonObject -> { + val obj = elem.asJsonObject + for ((k, v) in obj.entrySet()) { + val newKey = if (prefix.isBlank()) k else "$prefix.$k" + flattenJson(v, newKey, out) + } + } + elem.isJsonArray -> { + val arr = elem.asJsonArray + for (i in 0 until arr.size()) { + val newKey = "$prefix[$i]" + flattenJson(arr[i], newKey, out) + } + } + } + } +} diff --git a/app/src/main/java/com/example/myapplication/network/security/NonceUtils.kt b/app/src/main/java/com/example/myapplication/network/security/NonceUtils.kt new file mode 100644 index 0000000..8167499 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/network/security/NonceUtils.kt @@ -0,0 +1,11 @@ +package com.example.myapplication.network.security + +import java.util.UUID + +object NonceUtils { + fun genNonce(): String = + UUID.randomUUID().toString().replace("-", "").take(16) + + fun genTimestampSeconds(): String = + (System.currentTimeMillis() / 1000).toString() +} diff --git a/app/src/main/java/com/example/myapplication/network/security/SignUtils.kt b/app/src/main/java/com/example/myapplication/network/security/SignUtils.kt new file mode 100644 index 0000000..35bfb1b --- /dev/null +++ b/app/src/main/java/com/example/myapplication/network/security/SignUtils.kt @@ -0,0 +1,38 @@ +package com.example.myapplication.network.security + +import java.net.URLEncoder +import javax.crypto.Mac +import javax.crypto.spec.SecretKeySpec + +object SignUtils { + + fun calcSign(params: Map, secret: String): String { + val signStr = buildSignString(params, secret) + return hmacSha256Hex(signStr, secret) + } + + fun buildSignString(params: Map, secret: String): String { + val filtered = params + .filter { (k, v) -> v.isNotBlank() && !k.equals("sign", ignoreCase = true) } + .toSortedMap() + + val sb = StringBuilder() + filtered.forEach { (k, v) -> + if (sb.isNotEmpty()) sb.append("&") + sb.append(k).append("=").append(urlEncode(v)) + } + sb.append("&secret=").append(urlEncode(secret)) + return sb.toString() + } + + private fun hmacSha256Hex(data: String, secret: String): String { + val mac = Mac.getInstance("HmacSHA256") + val keySpec = SecretKeySpec(secret.toByteArray(Charsets.UTF_8), "HmacSHA256") + mac.init(keySpec) + val bytes = mac.doFinal(data.toByteArray(Charsets.UTF_8)) + return bytes.joinToString("") { "%02x".format(it) } + } + + private fun urlEncode(v: String): String = + URLEncoder.encode(v, "UTF-8") +} diff --git a/app/src/main/java/com/example/myapplication/ui/circle/CircleFragment.kt b/app/src/main/java/com/example/myapplication/ui/circle/CircleFragment.kt deleted file mode 100644 index 7df92d3..0000000 --- a/app/src/main/java/com/example/myapplication/ui/circle/CircleFragment.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.example.myapplication.ui.circle - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment -import com.example.myapplication.R - -class CircleFragment : Fragment() { - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - return inflater.inflate(R.layout.fragment_circle, container, false) - } -} diff --git a/app/src/main/java/com/example/myapplication/ui/common/LoadingOverlay.kt b/app/src/main/java/com/example/myapplication/ui/common/LoadingOverlay.kt index c5f562f..65e789a 100644 --- a/app/src/main/java/com/example/myapplication/ui/common/LoadingOverlay.kt +++ b/app/src/main/java/com/example/myapplication/ui/common/LoadingOverlay.kt @@ -1,5 +1,7 @@ package com.example.myapplication.ui.common +import android.os.Looper +import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -12,20 +14,33 @@ class LoadingOverlay private constructor( companion object { fun attach(parent: ViewGroup): LoadingOverlay { val overlay = LayoutInflater.from(parent.context) - .inflate(R.layout.view_fullscreen_loading, parent, false) + .inflate(R.layout.view_fullscreen_loading, parent, false).apply { + visibility = View.GONE + bringToFront() + elevation = 100f // 确保在其它视图之上 + } - overlay.visibility = View.GONE - parent.addView(overlay) // 加到最上层(最后添加的在最上面) + parent.addView(overlay) return LoadingOverlay(parent, overlay) } } fun show() { - overlay.visibility = View.VISIBLE + if (Looper.getMainLooper().thread == Thread.currentThread()) { + overlay.visibility = View.VISIBLE + } else { + overlay.post { overlay.visibility = View.VISIBLE } + } + Log.d("LoadingOverlay", "Show loading") } fun hide() { - overlay.visibility = View.GONE + if (Looper.getMainLooper().thread == Thread.currentThread()) { + overlay.visibility = View.GONE + } else { + overlay.post { overlay.visibility = View.GONE } + } + Log.d("LoadingOverlay", "Hide loading") } fun remove() { diff --git a/app/src/main/java/com/example/myapplication/ui/home/HomeFragment.kt b/app/src/main/java/com/example/myapplication/ui/home/HomeFragment.kt index 6de4209..3650d53 100644 --- a/app/src/main/java/com/example/myapplication/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/example/myapplication/ui/home/HomeFragment.kt @@ -25,13 +25,21 @@ import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView import androidx.viewpager2.widget.ViewPager2 import com.example.myapplication.ImeGuideActivity +import com.example.myapplication.ui.common.LoadingOverlay import com.example.myapplication.R -import com.example.myapplication.network.* +import com.example.myapplication.network.AddPersonaClick +import com.example.myapplication.network.AuthEvent +import com.example.myapplication.network.AuthEventBus +import com.example.myapplication.network.BehaviorReporter +import com.example.myapplication.network.RetrofitClient +import com.example.myapplication.network.listByTagWithNotLogin import com.example.myapplication.utils.EncryptedSharedPreferencesUtil import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.card.MaterialCardView import kotlinx.coroutines.Job import kotlinx.coroutines.launch +import com.example.myapplication.network.PersonaClick +import com.example.myapplication.ui.home.PersonaAdapter import kotlin.math.abs class HomeFragment : Fragment() { @@ -47,6 +55,7 @@ class HomeFragment : Fragment() { private lateinit var tabList2: TextView private lateinit var backgroundImage: ImageView private var lastList1RenderKey: String? = null + private lateinit var loadingOverlay: LoadingOverlay private var preloadJob: Job? = null private var allPersonaCache: List = emptyList() @@ -85,6 +94,7 @@ class HomeFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { AuthEventBus.events.collect { event -> @@ -95,13 +105,13 @@ class HomeFragment : Fragment() { personaCache.clear() allPersonaCache = emptyList() lastList1RenderKey = null - + // 2) 重新拉列表1(登录态接口会变) viewLifecycleOwner.lifecycleScope.launch { allPersonaCache = fetchAllPersonaList() notifyPageChangedOnMain(0) } - + // 3) 如果当前在某个 tag 页,也建议重新拉当前页数据 val pos = viewPager.currentItem if (pos > 0) { @@ -114,21 +124,100 @@ class HomeFragment : Fragment() { } } } + + is AuthEvent.CharacterAdded -> { + viewLifecycleOwner.lifecycleScope.launch { + loadingOverlay.show() + try { + // 1) 列表一:重新拉 + allPersonaCache = fetchAllPersonaList() + lastList1RenderKey = null + notifyPageChangedOnMain(0) + + // 2) 列表二:清缓存(最可靠,避免“删除后还显示旧数据”) + personaCache.clear() + + // 3) 如果当前就在某个 tag 页:让它立刻更新(显示 loading -> 拉取 -> 刷新) + val pos = viewPager.currentItem + if (pos > 0) { + val tagId = tags.getOrNull(pos - 1)?.id + if (tagId != null) { + // 先刷新一次,让页面进入 loading(因为缓存被清了) + notifyPageChangedOnMain(pos) + + // 再拉当前 tag 的新数据 + val list = fetchPersonaByTag(tagId) + personaCache[tagId] = list + notifyPageChangedOnMain(pos) + } + } + + // 4) 可选:你如果希望“删除后列表二也立刻全量更新”,就顺手再预加载一遍 + startPreloadAllTagsFillCacheOnly() + } finally { + loadingOverlay.hide() + } + } + } + + is AuthEvent.CharacterDeleted -> { + viewLifecycleOwner.lifecycleScope.launch { + loadingOverlay.show() + try { + // 1) 列表一:重新拉 + allPersonaCache = fetchAllPersonaList() + lastList1RenderKey = null + notifyPageChangedOnMain(0) + + // 2) 列表二:清缓存(最可靠,避免“删除后还显示旧数据”) + personaCache.clear() + + // 3) 如果当前就在某个 tag 页:让它立刻更新(显示 loading -> 拉取 -> 刷新) + val pos = viewPager.currentItem + if (pos > 0) { + val tagId = tags.getOrNull(pos - 1)?.id + if (tagId != null) { + // 先刷新一次,让页面进入 loading(因为缓存被清了) + notifyPageChangedOnMain(pos) + + // 再拉当前 tag 的新数据 + val list = fetchPersonaByTag(tagId) + personaCache[tagId] = list + notifyPageChangedOnMain(pos) + } + } + + // 4) 可选:你如果希望“删除后列表二也立刻全量更新”,就顺手再预加载一遍 + startPreloadAllTagsFillCacheOnly() + } finally { + loadingOverlay.hide() + } + } + } else -> Unit } } } } - // 充值按钮点击 - 使用事件总线打开全局页面 view.findViewById(R.id.rechargeButton).setOnClickListener { + BehaviorReporter.report( + isNewUser = false, + "page_id" to "home_main", + "element_id" to "buy_vip_btn", + ) AuthEventBus.emit(AuthEvent.OpenGlobalPage(R.id.rechargeFragment)) } // 输入法激活跳转 view.findViewById(R.id.floatingImage).setOnClickListener { if (!isAdded) return@setOnClickListener + BehaviorReporter.report( + isNewUser = false, + "page_id" to "home_main", + "element_id" to "permission_float_btn", + ) startActivity(Intent(requireActivity(), ImeGuideActivity::class.java)) } @@ -141,11 +230,13 @@ class HomeFragment : Fragment() { tabList2 = view.findViewById(R.id.tab_list2) viewPager = view.findViewById(R.id.viewPager) viewPager.isSaveEnabled = false - viewPager.offscreenPageLimit = 2 + viewPager.offscreenPageLimit = 2 backgroundImage = bottomSheet.findViewById(R.id.backgroundImage) val root = view.findViewById(R.id.rootCoordinator) val floatingImage = view.findViewById(R.id.floatingImage) + loadingOverlay = LoadingOverlay.attach(root) + Log.d("HomeFragment", "LoadingOverlay initialized") root.post { if (!isAdded) return@post @@ -169,22 +260,26 @@ class HomeFragment : Fragment() { // 加载列表一 viewLifecycleOwner.lifecycleScope.launch { + loadingOverlay.show() try { val list = fetchAllPersonaList() if (!isAdded) return@launch allPersonaCache = list - + // ✅ 关键:数据变了就清 renderKey,允许重建一次 UI lastList1RenderKey = null - + notifyPageChangedOnMain(0) } catch (e: Exception) { Log.e("1314520-HomeFragment", "获取列表一失败", e) + } finally { + loadingOverlay.hide() } } // 拉标签 + 预加载 viewLifecycleOwner.lifecycleScope.launch { + loadingOverlay.show() try { val response = RetrofitClient.apiService.tagList() if (!isAdded) return@launch @@ -204,11 +299,13 @@ class HomeFragment : Fragment() { startPreloadAllTagsFillCacheOnly() } catch (e: Exception) { Log.e("1314520-HomeFragment", "获取标签失败", e) + } finally { + loadingOverlay.hide() } } } - // ================== 你要求的核心优化:setupViewPager 只初始化一次 ================== + // ================== 核心:setupViewPager 只初始化一次 ================== private fun setupViewPagerOnce() { if (sheetAdapter != null) return @@ -223,7 +320,7 @@ class HomeFragment : Fragment() { override fun onPageSelected(position: Int) { if (!isAdded) return updateTabsAndTags(position) - + // ✅ 修复:当切换到标签页且缓存已有数据时,强制刷新UI if (position > 0) { val tagIndex = position - 1 @@ -245,6 +342,52 @@ class HomeFragment : Fragment() { } } + // ---------------- 方案A:成功后“造新数据(copy)替换缓存”并刷新 ---------------- + + private fun applyAddedToggle(personaId: Int, newAdded: Boolean) { + // 1) 更新列表一缓存 + run { + val oldAll = allPersonaCache + val idxAll = oldAll.indexOfFirst { it.id == personaId } + if (idxAll >= 0) { + val newList = oldAll.toMutableList() + val oldItem = newList[idxAll] + newList[idxAll] = oldItem.copy(added = newAdded) + allPersonaCache = newList + + // renderList1 有 renderKey,必须清一下 + lastList1RenderKey = null + notifyPageChangedOnMain(0) + } + } + + // 2) 更新所有 tag 缓存(personaCache) + val keys = personaCache.keys.toList() + var changedCurrentTagPage = false + + for (tagId in keys) { + val old = personaCache[tagId] ?: continue + val idx = old.indexOfFirst { it.id == personaId } + if (idx >= 0) { + val newList = old.toMutableList() + val oldItem = newList[idx] + newList[idx] = oldItem.copy(added = newAdded) + personaCache[tagId] = newList + + // 如果当前就在这个 tag 页,标记需要刷新 + val pos = viewPager.currentItem + val currentTagId = tags.getOrNull(pos - 1)?.id + if (pos > 0 && currentTagId == tagId) { + changedCurrentTagPage = true + } + } + } + + if (changedCurrentTagPage) { + notifyPageChangedOnMain(viewPager.currentItem) + } + } + // ---------------- 拖拽效果 ---------------- private fun initDrag(target: View, parent: ViewGroup) { @@ -328,62 +471,52 @@ class HomeFragment : Fragment() { private fun setupBottomSheet(root: View) { bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet) - + bottomSheetBehavior.isDraggable = true bottomSheetBehavior.isHideable = false bottomSheetBehavior.isFitToContents = false bottomSheetBehavior.halfExpandedRatio = 0.7f - - root.post { - if (!isAdded) return@post - val coordinatorHeight = root.height - 40 - val button = root.findViewById(R.id.rechargeButton) - val peek = (coordinatorHeight - button.bottom).coerceAtLeast(200) - bottomSheetBehavior.peekHeight = peek - bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED - } - + + // ✅ 固定初始高度,避免每次计算导致跳动 + bottomSheetBehavior.peekHeight = dpToPx(260) // 你想要多少就写多少 + bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED + bottomSheetBehavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() { override fun onStateChanged(bottomSheet: View, newState: Int) { scrim.isVisible = newState != BottomSheetBehavior.STATE_COLLAPSED } - override fun onSlide(bottomSheet: View, slideOffset: Float) { if (slideOffset >= 0f) scrim.alpha = slideOffset.coerceIn(0f, 1f) } }) - - scrim.setOnClickListener { - bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED - } - - scrim.setOnTouchListener { _, event -> - if (event.action == MotionEvent.ACTION_MOVE) { - bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED - true - } else false - } - - header.setOnClickListener { - when (bottomSheetBehavior.state) { - BottomSheetBehavior.STATE_COLLAPSED -> - bottomSheetBehavior.state = BottomSheetBehavior.STATE_HALF_EXPANDED - - BottomSheetBehavior.STATE_HALF_EXPANDED, - BottomSheetBehavior.STATE_EXPANDED -> - bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED - - else -> {} - } - } + } + + private fun dpToPx(dp: Int): Int { + return (dp * resources.displayMetrics.density).toInt() } // ---------------- Tabs ---------------- private fun setupTopTabs() { - tabList1.setOnClickListener { viewPager.currentItem = 0 } + tabList1.setOnClickListener { + viewPager.currentItem = 0 + + BehaviorReporter.report( + isNewUser = false, + "page_id" to "home_rank", + ) + + BehaviorReporter.report( + isNewUser = false, + "page_id" to "home_rank_content", + ) + } tabList2.setOnClickListener { if (tags.isNotEmpty()) viewPager.currentItem = 1 + BehaviorReporter.report( + isNewUser = false, + "page_id" to "home_hot", + ) } } @@ -488,28 +621,28 @@ class HomeFragment : Fragment() { private fun startPreloadAllTagsFillCacheOnly() { preloadJob?.cancel() - + val semaphore = kotlinx.coroutines.sync.Semaphore(permits = 2) - + preloadJob = viewLifecycleOwner.lifecycleScope.launch { if (tags.isEmpty()) return@launch - + tags.forEach { tag -> if (personaCache.containsKey(tag.id)) return@forEach - + launch { semaphore.acquire() try { val list = fetchPersonaByTag(tag.id) personaCache[tag.id] = list - + // ✅ 只在用户正在看的页时刷新一次(不算乱刷 UI) val idx = tags.indexOfFirst { it.id == tag.id } val thisPos = 1 + idx if (idx >= 0 && viewPager.currentItem == thisPos) { notifyPageChangedOnMain(thisPos) } - + } catch (e: Exception) { Log.e("HomeFragment", "preload tag=${tag.id} fail", e) } finally { @@ -519,7 +652,6 @@ class HomeFragment : Fragment() { } } } - // ---------------- ViewPager Adapter ---------------- @@ -531,10 +663,10 @@ class HomeFragment : Fragment() { fun updatePageCount(newCount: Int) { if (newCount == pageCount) return - + val old = pageCount pageCount = newCount - + if (newCount > old) { notifyItemRangeInserted(old, newCount - old) } else { @@ -564,10 +696,7 @@ class HomeFragment : Fragment() { val loadingView = root.findViewById(R.id.loadingView) rv2.setHasFixedSize(true) - - // ✅ 禁止 itemAnimator(减少 layout 抖动) rv2.itemAnimator = null - rv2.isNestedScrollingEnabled = false var adapter = rv2.adapter as? PersonaAdapter @@ -584,22 +713,31 @@ class HomeFragment : Fragment() { is PersonaClick.Add -> { viewLifecycleOwner.lifecycleScope.launch { + val personaId = click.persona.id?: return@launch + val oldAdded = click.persona.added + val newAdded = !oldAdded try { - if (click.persona.added == true) { - click.persona.id?.let { id -> - RetrofitClient.apiService.delUserCharacter(id.toInt()) - } - } else { + if (oldAdded) { + // RetrofitClient.apiService.delUserCharacter(personaId) + // // ✅ 成功后替换缓存并刷新 + // applyAddedToggle(personaId, newAdded) + } + else { + Log.d("1314520-HomeFragment", "add persona id=${click.persona.id}") val req = AddPersonaClick( - characterId = click.persona.id?.toInt() ?: 0, + characterId = personaId, emoji = click.persona.emoji ?: "" ) RetrofitClient.apiService.addUserCharacter(req) + // ✅ 成功后替换缓存并刷新 + applyAddedToggle(personaId, newAdded) } - } catch (_: Exception) { + } catch (e: Exception) { + Log.e("1314520-HomeFragment", "grid toggle add failed id=$personaId", e) } } } + else -> Unit } } rv2.layoutManager = GridLayoutManager(root.context, 2) @@ -629,7 +767,7 @@ class HomeFragment : Fragment() { override fun getItemCount(): Int = pageCount } - // ---------------- 列表一渲染(原逻辑不动) ---------------- + // ---------------- 列表一渲染 ---------------- private fun renderList1(root: View, list: List) { val key = buildString { @@ -641,6 +779,7 @@ class HomeFragment : Fragment() { } if (key == lastList1RenderKey) return lastList1RenderKey = key + val sorted = list.sortedBy { it.rank ?: Int.MAX_VALUE } val top3 = sorted.take(3) val others = if (sorted.size > 3) sorted.drop(3) else emptyList() @@ -650,6 +789,7 @@ class HomeFragment : Fragment() { avatarId = R.id.avatar_first, nameId = R.id.name_first, addBtnId = R.id.btn_add_first, + addBtnIcon = R.id.add_first_icon, containerId = R.id.container_first, item = top3.getOrNull(0) ) @@ -659,6 +799,7 @@ class HomeFragment : Fragment() { avatarId = R.id.avatar_second, nameId = R.id.name_second, addBtnId = R.id.btn_add_second, + addBtnIcon = R.id.add_second_icon, containerId = R.id.container_second, item = top3.getOrNull(1) ) @@ -668,6 +809,7 @@ class HomeFragment : Fragment() { avatarId = R.id.avatar_third, nameId = R.id.name_third, addBtnId = R.id.btn_add_third, + addBtnIcon = R.id.add_third_icon, containerId = R.id.container_third, item = top3.getOrNull(2) ) @@ -686,6 +828,62 @@ class HomeFragment : Fragment() { val iv = itemView.findViewById(R.id.iv_avatar) com.bumptech.glide.Glide.with(iv).load(p.avatarUrl).into(iv) + // ---------------- add 按钮(失败回滚 + 防连点) ---------------- + val addBtn = itemView.findViewById(R.id.btn_add) + val addIcon = itemView.findViewById(R.id.add_icon) + + val originBg = addBtn.background + val originIcon = addIcon.drawable + + fun renderAddState(added: Boolean) { + if (added) { + addBtn.setBackgroundResource(R.drawable.round_bg_others_already) + addIcon.setImageResource(R.drawable.ime_guide_activity_btn_completed_img) + } else { + addBtn.background = originBg + addIcon.setImageDrawable(originIcon) + } + } + + // ✅ 首次渲染 + renderAddState(p.added == true) + + addBtn.setOnClickListener { + if (!addBtn.isEnabled) return@setOnClickListener + + viewLifecycleOwner.lifecycleScope.launch { + val personaId = p.id?: return@launch + val oldAdded = p.added + val newAdded = !oldAdded + + addBtn.isEnabled = false + renderAddState(newAdded) + try { + if (oldAdded) { + // RetrofitClient.apiService.delUserCharacter(personaId) + // // ✅ 只有成功才更新缓存 + 更新UI(失败则保持原样) + // applyAddedToggle(personaId, newAdded) + } + else { + Log.d("1314520-HomeFragment", "add persona id=${p.id}") + val req = AddPersonaClick( + characterId = personaId, + emoji = p.emoji ?: "" + ) + RetrofitClient.apiService.addUserCharacter(req) + // ✅ 只有成功才更新缓存 + 更新UI(失败则保持原样) + applyAddedToggle(personaId, newAdded) + } + } catch (e: Exception) { + Log.e("1314520-HomeFragment", "others toggle add failed id=$personaId", e) + renderAddState(oldAdded) + } finally { + addBtn.isEnabled = true + } + } + } + + // ---------------- item 点击 ---------------- itemView.setOnClickListener { if (!isAdded || childFragmentManager.isStateSaved) return@setOnClickListener PersonaDetailDialogFragment @@ -693,23 +891,6 @@ class HomeFragment : Fragment() { .show(childFragmentManager, "persona_detail") } - itemView.findViewById(R.id.btn_add).setOnClickListener { - viewLifecycleOwner.lifecycleScope.launch { - try { - if (p.added == true) { - p.id?.let { id -> RetrofitClient.apiService.delUserCharacter(id.toInt()) } - } else { - val req = AddPersonaClick( - characterId = p.id?.toInt() ?: 0, - emoji = p.emoji ?: "" - ) - RetrofitClient.apiService.addUserCharacter(req) - } - } catch (_: Exception) { - } - } - } - container.addView(itemView) } } @@ -719,12 +900,14 @@ class HomeFragment : Fragment() { avatarId: Int, nameId: Int, addBtnId: Int, + addBtnIcon: Int, containerId: Int, item: listByTagWithNotLogin? ) { val avatar = root.findViewById(avatarId) val name = root.findViewById(nameId) - val addBtn = root.findViewById(addBtnId) + val addBtn = root.findViewById(addBtnId) + val addIcon = root.findViewById(addBtnIcon) val container = root.findViewById(containerId) if (item == null) { @@ -739,19 +922,60 @@ class HomeFragment : Fragment() { name.text = item.characterName ?: "" com.bumptech.glide.Glide.with(avatar).load(item.avatarUrl).into(avatar) + // ✅ 记录“原始背景/原始icon”,用于 added=false 时恢复 + val originBg = addBtn.background + val originIconRes = when (addBtnId) { + R.id.btn_add_first -> R.drawable.first_add + R.id.btn_add_second -> R.drawable.second_add + R.id.btn_add_third -> R.drawable.third_add + else -> 0 + } + + fun renderAddState(added: Boolean) { + if (added) { + addBtn.setBackgroundResource(R.drawable.round_bg_others_already) + addIcon.setImageResource(R.drawable.ime_guide_activity_btn_completed_img) + } else { + addBtn.background = originBg + if (originIconRes != 0) addIcon.setImageResource(originIconRes) + } + } + + // ✅ 首次渲染 + renderAddState(item.added == true) + + // ✅ 点击:失败回滚 + 防连点(请求中禁用按钮) addBtn.setOnClickListener { + if (!addBtn.isEnabled) return@setOnClickListener + viewLifecycleOwner.lifecycleScope.launch { + val personaId = item.id?: return@launch + val oldAdded = item.added + val newAdded = !oldAdded + + addBtn.isEnabled = false + renderAddState(newAdded) try { - if (item.added == true) { - item.id?.let { id -> RetrofitClient.apiService.delUserCharacter(id.toInt()) } - } else { + if (oldAdded) { + // RetrofitClient.apiService.delUserCharacter(personaId) + // // ✅ 只有成功才更新缓存 + 更新UI + // applyAddedToggle(personaId, newAdded) + } + else { + Log.d("1314520-HomeFragment", "add persona id=${item.id}") val req = AddPersonaClick( - characterId = item.id?.toInt() ?: 0, + characterId = personaId, emoji = item.emoji ?: "" ) RetrofitClient.apiService.addUserCharacter(req) + // ✅ 只有成功才更新缓存 + 更新UI + applyAddedToggle(personaId, newAdded) } - } catch (_: Exception) { + } catch (e: Exception) { + Log.e("1314520-HomeFragment", "others toggle add failed id=$personaId", e) + renderAddState(oldAdded) + } finally { + addBtn.isEnabled = true } } } diff --git a/app/src/main/java/com/example/myapplication/ui/home/PersonaAdapter.kt b/app/src/main/java/com/example/myapplication/ui/home/PersonaAdapter.kt index 1a479f6..d2a0f2e 100644 --- a/app/src/main/java/com/example/myapplication/ui/home/PersonaAdapter.kt +++ b/app/src/main/java/com/example/myapplication/ui/home/PersonaAdapter.kt @@ -3,14 +3,15 @@ package com.example.myapplication.ui.home import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.ImageView +import android.widget.LinearLayout import android.widget.TextView import androidx.recyclerview.widget.RecyclerView import com.bumptech.glide.Glide import com.example.myapplication.R -import com.example.myapplication.network.listByTagWithNotLogin import com.example.myapplication.network.PersonaClick +import com.example.myapplication.network.listByTagWithNotLogin import de.hdodenhof.circleimageview.CircleImageView -import android.util.Log class PersonaAdapter( private val onClick: (PersonaClick) -> Unit @@ -26,35 +27,37 @@ class PersonaAdapter( inner class VH(itemView: View) : RecyclerView.ViewHolder(itemView) { - val ivAvatar: CircleImageView = itemView.findViewById(R.id.ivAvatar) - val tvName: TextView = itemView.findViewById(R.id.tvName) - val characterBackground: TextView = - itemView.findViewById(R.id.characterBackground) - val download: TextView = itemView.findViewById(R.id.download) - val operation: TextView = itemView.findViewById(R.id.operation) + private val ivAvatar: CircleImageView = itemView.findViewById(R.id.ivAvatar) + private val tvName: TextView = itemView.findViewById(R.id.tvName) + private val characterBackground: TextView = itemView.findViewById(R.id.characterBackground) + private val download: TextView = itemView.findViewById(R.id.download) + private val operation: LinearLayout = itemView.findViewById(R.id.operation) + private val operationIcon: ImageView = itemView.findViewById(R.id.operation_add_icon) - /** ✅ 统一绑定 + 点击逻辑 */ fun bind(item: listByTagWithNotLogin) { - tvName.text = item.characterName characterBackground.text = item.characterBackground download.text = item.download - + Glide.with(itemView.context) .load(item.avatarUrl) .placeholder(R.drawable.default_avatar) .error(R.drawable.default_avatar) .into(ivAvatar) - - // ✅ 整个 item:跳详情 - itemView.setOnClickListener { - onClick(PersonaClick.Item(item)) - } - - // ✅ 添加 / 下载按钮 - operation.setOnClickListener { - onClick(PersonaClick.Add(item)) - } + + val isAdded = item.added + + // ✅ 背景改 operation(外层容器) + operation.setBackgroundResource( + if (isAdded) R.drawable.list_two_bg_already else R.drawable.list_two_bg + ) + + // ✅ 图标改 operationIcon(中间图) + operationIcon.setImageResource( + if (isAdded) R.drawable.ime_guide_activity_btn_completed_img else R.drawable.operation_add + ) + itemView.setOnClickListener { onClick(PersonaClick.Item(item)) } + operation.setOnClickListener { onClick(PersonaClick.Add(item)) } } } diff --git a/app/src/main/java/com/example/myapplication/ui/home/PersonaDetailDialogFragment.kt b/app/src/main/java/com/example/myapplication/ui/home/PersonaDetailDialogFragment.kt index b8a2663..6482429 100644 --- a/app/src/main/java/com/example/myapplication/ui/home/PersonaDetailDialogFragment.kt +++ b/app/src/main/java/com/example/myapplication/ui/home/PersonaDetailDialogFragment.kt @@ -15,6 +15,9 @@ import com.example.myapplication.network.RetrofitClient import com.example.myapplication.network.CharacterDetailResponse import kotlinx.coroutines.launch import com.example.myapplication.network.AddPersonaClick +import android.util.Log +import com.example.myapplication.network.AuthEvent +import com.example.myapplication.network.AuthEventBus class PersonaDetailDialogFragment : DialogFragment() { @@ -59,8 +62,9 @@ class PersonaDetailDialogFragment : DialogFragment() { tvName.text = data.characterName ?: "" download.text = data.download ?: "" tvBackground.text = data.characterBackground ?: "" - btnAdd.text = data.added?.let { "Added" } ?: "Add" - btnAdd.setBackgroundResource(data.added?.let { R.drawable.ic_added } ?: R.drawable.keyboard_ettings) + btnAdd.text = if (data.added == true) "Added" else "Add" + btnAdd.setBackgroundResource(if (data.added == true) R.drawable.ic_added else R.drawable.keyboard_ettings) + val newAdded = !(data.added ?: false) Glide.with(requireContext()) .load(data.avatarUrl) @@ -72,13 +76,13 @@ class PersonaDetailDialogFragment : DialogFragment() { lifecycleScope.launch { if(data.added == true){ //取消收藏 - data.id?.let { id -> - try { - RetrofitClient.apiService.delUserCharacter(id.toInt()) - } catch (e: Exception) { - // 处理错误 - } - } + // data.id?.let { id -> + // try { + // RetrofitClient.apiService.delUserCharacter(id.toInt()) + // } catch (e: Exception) { + // // 处理错误 + // } + // } }else{ val addPersonaRequest = AddPersonaClick( characterId = data.id?.toInt() ?: 0, @@ -86,8 +90,11 @@ class PersonaDetailDialogFragment : DialogFragment() { ) try { RetrofitClient.apiService.addUserCharacter(addPersonaRequest) + data.id?.let { personaId -> + AuthEventBus.emit(AuthEvent.CharacterAdded(personaId,newAdded)) + } } catch (e: Exception) { - // 处理错误 + Log.e("1314520-PersonaDetailDialogFragment", "addUserCharacter error", e) } } dismissAllowingStateLoss() diff --git a/app/src/main/java/com/example/myapplication/ui/keyboard/ConfirmDeleteDialogFragment.kt b/app/src/main/java/com/example/myapplication/ui/keyboard/ConfirmDeleteDialogFragment.kt new file mode 100644 index 0000000..98801d9 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/ui/keyboard/ConfirmDeleteDialogFragment.kt @@ -0,0 +1,72 @@ +package com.example.myapplication.ui.keyboard + +import android.app.Dialog +import android.os.Bundle +import android.view.LayoutInflater +import android.view.Window +import android.widget.TextView +import androidx.fragment.app.DialogFragment +import com.example.myapplication.R + +class ConfirmDeleteDialogFragment : DialogFragment() { + + companion object { + private const val ARG_NAME = "arg_name" + private const val ARG_EMOJI = "arg_emoji" + + fun newInstance( + characterName: String?, + emoji: String?, + onConfirm: () -> Unit + ): ConfirmDeleteDialogFragment { + return ConfirmDeleteDialogFragment().apply { + this.onConfirm = onConfirm + arguments = Bundle().apply { + putString(ARG_NAME, characterName ?: "") + putString(ARG_EMOJI, emoji ?: "🙂") + } + } + } + } + + private var onConfirm: (() -> Unit)? = null + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val dialog = Dialog(requireContext()) + dialog.requestWindowFeature(Window.FEATURE_NO_TITLE) + + val v = LayoutInflater.from(requireContext()) + .inflate(R.layout.dialog_confirm_delete_character, null, false) + dialog.setContentView(v) + + dialog.setCancelable(true) + dialog.setCanceledOnTouchOutside(true) + + val name = arguments?.getString(ARG_NAME).orEmpty() + val emoji = arguments?.getString(ARG_EMOJI) ?: "🙂" + + v.findViewById(R.id.tv_name).text = name + v.findViewById(R.id.tv_emoji).text = emoji + + v.findViewById(R.id.btn_cancel).setOnClickListener { + dismissAllowingStateLoss() + } + + v.findViewById(R.id.btn_confirm).setOnClickListener { + dismissAllowingStateLoss() + onConfirm?.invoke() + } + + // 宽度更贴合弹窗 + dialog.window?.setLayout( + (resources.displayMetrics.widthPixels * 0.86f).toInt(), + android.view.ViewGroup.LayoutParams.WRAP_CONTENT + ) + return dialog + } + + override fun onDestroyView() { + super.onDestroyView() + onConfirm = null + } +} diff --git a/app/src/main/java/com/example/myapplication/ui/keyboard/DragSortCallback.kt b/app/src/main/java/com/example/myapplication/ui/keyboard/DragSortCallback.kt new file mode 100644 index 0000000..46b066f --- /dev/null +++ b/app/src/main/java/com/example/myapplication/ui/keyboard/DragSortCallback.kt @@ -0,0 +1,68 @@ +package com.example.myapplication.ui.keyboard + +import android.view.HapticFeedbackConstants +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.RecyclerView + +class DragSortCallback( + private val onMove: (from: Int, to: Int) -> Unit +) : ItemTouchHelper.Callback() { + + private var didHaptic = false + + override fun isLongPressDragEnabled(): Boolean = true + override fun isItemViewSwipeEnabled(): Boolean = false + + override fun getMovementFlags( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder + ): Int { + val dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN or + ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT + return makeMovementFlags(dragFlags, 0) + } + + override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) { + super.onSelectedChanged(viewHolder, actionState) + + if (actionState == ItemTouchHelper.ACTION_STATE_DRAG && viewHolder != null) { + // ✅ 触觉反馈(只触发一次) + if (!didHaptic) { + viewHolder.itemView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) + didHaptic = true + } + // ✅ 视觉反馈:放大 + 半透明 + viewHolder.itemView.animate() + .scaleX(1.03f).scaleY(1.03f) + .alpha(0.85f) + .setDuration(120) + .start() + } + } + + override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) { + super.clearView(recyclerView, viewHolder) + didHaptic = false + + // ✅ 结束拖动,恢复视觉 + viewHolder.itemView.animate() + .scaleX(1f).scaleY(1f) + .alpha(1f) + .setDuration(120) + .start() + } + + override fun onMove( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder + ): Boolean { + val from = viewHolder.adapterPosition + val to = target.adapterPosition + if (from == RecyclerView.NO_POSITION || to == RecyclerView.NO_POSITION) return false + onMove(from, to) + return true + } + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {} +} diff --git a/app/src/main/java/com/example/myapplication/ui/keyboard/KeyboardAdapter.kt b/app/src/main/java/com/example/myapplication/ui/keyboard/KeyboardAdapter.kt new file mode 100644 index 0000000..5688e5d --- /dev/null +++ b/app/src/main/java/com/example/myapplication/ui/keyboard/KeyboardAdapter.kt @@ -0,0 +1,57 @@ +package com.example.myapplication.ui.keyboard + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.example.myapplication.R +import com.example.myapplication.network.ListByUserWithNot + +class KeyboardAdapter( + private val onItemClick: (ListByUserWithNot) -> Unit +) : RecyclerView.Adapter() { + + private val items = mutableListOf() + + fun submitList(newList: List) { + items.clear() + items.addAll(newList) + notifyDataSetChanged() + } + + fun getCurrentIdsInOrder(): List = items.map { it.id } + + fun moveItem(from: Int, to: Int) { + if (from == to) return + val item = items.removeAt(from) + items.add(to, item) + notifyItemMoved(from, to) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH { + val v = LayoutInflater.from(parent.context) + .inflate(R.layout.item_keyboard_character, parent, false) + return VH(v) + } + + override fun onBindViewHolder(holder: VH, position: Int) { + holder.bind(items[position]) + } + + override fun getItemCount(): Int = items.size + + inner class VH(itemView: View) : RecyclerView.ViewHolder(itemView) { + private val root: View = itemView.findViewById(R.id.item_root) + private val tvEmoji: TextView = itemView.findViewById(R.id.tv_emoji) + private val tvName: TextView = itemView.findViewById(R.id.tv_name) + + fun bind(item: ListByUserWithNot) { + tvEmoji.text = item.emoji ?: "🙂" + tvName.text = item.characterName ?: "" + + // ✅ 点击整卡(不直接删,交给外层弹窗确认) + root.setOnClickListener { onItemClick(item) } + } + } +} diff --git a/app/src/main/java/com/example/myapplication/ui/keyboard/KeyboardDetailFragment.kt b/app/src/main/java/com/example/myapplication/ui/keyboard/KeyboardDetailFragment.kt index 3cdda23..d9c53ca 100644 --- a/app/src/main/java/com/example/myapplication/ui/keyboard/KeyboardDetailFragment.kt +++ b/app/src/main/java/com/example/myapplication/ui/keyboard/KeyboardDetailFragment.kt @@ -42,6 +42,7 @@ import java.io.BufferedInputStream import java.io.FileInputStream import com.example.myapplication.ui.shop.ShopEvent import com.example.myapplication.ui.shop.ShopEventBus +import com.example.myapplication.network.BehaviorReporter class KeyboardDetailFragment : Fragment() { @@ -57,6 +58,7 @@ class KeyboardDetailFragment : Fragment() { private lateinit var enabledButtonText: TextView private lateinit var progressBar: android.widget.ProgressBar private lateinit var swipeRefreshLayout: SwipeRefreshLayout + private var themeDetailResp: themeDetail? = null override fun onCreateView( inflater: LayoutInflater, @@ -80,7 +82,7 @@ class KeyboardDetailFragment : Fragment() { enabledButtonText = view.findViewById(R.id.enabledButtonText) progressBar = view.findViewById(R.id.progressBar) swipeRefreshLayout = view.findViewById(R.id.swipeRefreshLayout) - + // 设置按钮始终防止事件穿透的触摸监听器 enabledButton.setOnTouchListener { _, event -> // 如果按钮被禁用,消耗所有触摸事件防止穿透 @@ -120,6 +122,13 @@ class KeyboardDetailFragment : Fragment() { viewLifecycleOwner.lifecycleScope.launch { enableTheme() } + BehaviorReporter.report( + isNewUser = false, + "page_id" to "skin_detail", + "element_id" to "download_btn", + "theme_id" to themeId, + "purchased" to if (themeDetailResp?.isPurchased == true) "1" else "0", + ) } } @@ -132,7 +141,7 @@ class KeyboardDetailFragment : Fragment() { viewLifecycleOwner.lifecycleScope.launch { try { - val themeDetailResp = getThemeDetail(themeId)?.data + themeDetailResp = getThemeDetail(themeId)?.data val recommendThemeListResp = getrecommendThemeList()?.data Glide.with(requireView().context) @@ -333,6 +342,13 @@ class KeyboardDetailFragment : Fragment() { viewLifecycleOwner.lifecycleScope.launch { setpurchaseTheme(themeId) dialog.dismiss() + BehaviorReporter.report( + isNewUser = false, + "page_id" to "skin_detail", + "element_id" to "download_btn", + "theme_id" to themeId, + "purchased" to if (themeDetailResp?.isPurchased == true) "1" else "0", + ) } } diff --git a/app/src/main/java/com/example/myapplication/ui/keyboard/MyKeyboard.kt b/app/src/main/java/com/example/myapplication/ui/keyboard/MyKeyboard.kt index fbbfb72..31694cb 100644 --- a/app/src/main/java/com/example/myapplication/ui/keyboard/MyKeyboard.kt +++ b/app/src/main/java/com/example/myapplication/ui/keyboard/MyKeyboard.kt @@ -1,28 +1,150 @@ package com.example.myapplication.ui.keyboard import android.os.Bundle +import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.fragment.app.Fragment import android.widget.FrameLayout +import android.widget.TextView +import android.widget.Toast +import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.RecyclerView import com.example.myapplication.R +import com.example.myapplication.network.ApiResponse +import com.example.myapplication.network.AuthEvent +import com.example.myapplication.network.AuthEventBus +import com.example.myapplication.ui.common.LoadingOverlay +import com.example.myapplication.network.ListByUserWithNot +import com.example.myapplication.network.RetrofitClient +import com.example.myapplication.network.updateUserCharacterSortRequest +import kotlinx.coroutines.launch +import com.example.myapplication.network.BehaviorReporter class MyKeyboard : Fragment() { - + + private lateinit var rv: RecyclerView + private lateinit var btnSave: TextView + private lateinit var adapter: KeyboardAdapter + private lateinit var loadingOverlay: LoadingOverlay + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ): View? { - return inflater.inflate(R.layout.my_keyboard, container, false) - } + ): View = inflater.inflate(R.layout.my_keyboard, container, false) override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - + view.findViewById(R.id.iv_close).setOnClickListener { - parentFragmentManager.popBackStack() + requireActivity().onBackPressedDispatcher.onBackPressed() + } + + rv = view.findViewById(R.id.rv_keyboard) + btnSave = view.findViewById(R.id.btn_keyboard) + + adapter = KeyboardAdapter( + onItemClick = { item -> + // ✅ 点击卡片:弹自定义确认弹窗 + ConfirmDeleteDialogFragment + .newInstance(item.characterName, item.emoji) { + // ✅ 用户确认后才删除 + viewLifecycleOwner.lifecycleScope.launch { + val resp = setdelUserCharacter(item.id) + if (resp?.code == 0 && resp.data == true) { + Toast.makeText(requireContext(),"Deleted successfully", Toast.LENGTH_SHORT).show() + AuthEventBus.emit(AuthEvent.CharacterDeleted(item.id)) + loadList() + } else { + Toast.makeText(requireContext(), resp?.message ?: "Delete failed", Toast.LENGTH_SHORT).show() + } + } + } + .show(parentFragmentManager, "confirm_delete") + } + ) + + rv.layoutManager = GridLayoutManager(requireContext(), 2) + rv.adapter = adapter + + // ✅ 长按拖动排序 + 反馈 + ItemTouchHelper( + DragSortCallback { from, to -> + adapter.moveItem(from, to) + } + ).attachToRecyclerView(rv) + + loadingOverlay = LoadingOverlay.attach(view as ViewGroup) + loadList() + + // ✅ Save:上传当前排序(id数组) + btnSave.setOnClickListener { + BehaviorReporter.report( + isNewUser = false, + "page_id" to "my_keyboard", + "element_id" to "save_btn", + ) + val sortIds = adapter.getCurrentIdsInOrder() + Log.d("MyKeyboard-sort", sortIds.toString()) + + viewLifecycleOwner.lifecycleScope.launch { + loadingOverlay.show() + try { + val body = updateUserCharacterSortRequest(sort = sortIds) + val resp = setupdateUserCharacterSort(body) + if (resp?.code == 0 && resp.data == true) { + requireActivity().onBackPressedDispatcher.onBackPressed() + Toast.makeText(requireContext(), "Sorting has been successfully modified.", Toast.LENGTH_SHORT).show() + } else { + Toast.makeText(requireContext(), resp?.message ?: "Save failed", Toast.LENGTH_SHORT).show() + } + } catch (e: Exception) { + Toast.makeText(requireContext(), "Network error: ${e.message}", Toast.LENGTH_SHORT).show() + } finally { + loadingOverlay.hide() + } + } + } + } + + private fun loadList() { + viewLifecycleOwner.lifecycleScope.launch { + loadingOverlay.show() + try { + val resp = getlistByUser() + if (resp?.code == 0 && resp.data != null) { + adapter.submitList(resp.data) + Log.d("1314520-list", resp.data.toString()) + } else { + Toast.makeText(requireContext(), resp?.message ?: "Load failed", Toast.LENGTH_SHORT).show() + } + } catch (e: Exception) { + Toast.makeText(requireContext(), "Load failed: ${e.message}", Toast.LENGTH_SHORT).show() + } finally { + loadingOverlay.hide() + } + } + } + + // 获取用户人设列表 + private suspend fun getlistByUser(): ApiResponse>? = + runCatching { RetrofitClient.apiService.listByUser() }.getOrNull() + + // 更新用户人设排序 + private suspend fun setupdateUserCharacterSort(body: updateUserCharacterSortRequest): ApiResponse? = + runCatching { RetrofitClient.apiService.updateUserCharacterSort(body) }.getOrNull() + + // 删除用户人设 + private suspend fun setdelUserCharacter(id: Int): ApiResponse? { + loadingOverlay.show() + return try { + runCatching { RetrofitClient.apiService.delUserCharacter(id) }.getOrNull() + } finally { + loadingOverlay.hide() } } } diff --git a/app/src/main/java/com/example/myapplication/ui/login/ForgetPasswordEmailFragment.kt b/app/src/main/java/com/example/myapplication/ui/login/ForgetPasswordEmailFragment.kt index a4935e6..9bc479c 100644 --- a/app/src/main/java/com/example/myapplication/ui/login/ForgetPasswordEmailFragment.kt +++ b/app/src/main/java/com/example/myapplication/ui/login/ForgetPasswordEmailFragment.kt @@ -18,6 +18,7 @@ import com.example.myapplication.network.SendVerifyCodeRequest import com.example.myapplication.network.RetrofitClient import com.example.myapplication.utils.EncryptedSharedPreferencesUtil import android.util.Log +import com.example.myapplication.network.BehaviorReporter class ForgetPasswordEmailFragment : Fragment() { @@ -55,6 +56,11 @@ class ForgetPasswordEmailFragment : Fragment() { } else if (!isValidEmail(email)) { Toast.makeText(activity, "The email address format is incorrect", Toast.LENGTH_SHORT).show() } else { + BehaviorReporter.report( + isNewUser = false, + "page_id" to "forgot_password_email", + "element_id" to "next_btn", + ) loadingOverlay?.show() viewLifecycleOwner.lifecycleScope.launch { try { diff --git a/app/src/main/java/com/example/myapplication/ui/login/ForgetPasswordResetFragment.kt b/app/src/main/java/com/example/myapplication/ui/login/ForgetPasswordResetFragment.kt index f7351e1..0589ae4 100644 --- a/app/src/main/java/com/example/myapplication/ui/login/ForgetPasswordResetFragment.kt +++ b/app/src/main/java/com/example/myapplication/ui/login/ForgetPasswordResetFragment.kt @@ -21,6 +21,7 @@ import com.example.myapplication.network.RetrofitClient import com.example.myapplication.ui.common.LoadingOverlay import com.example.myapplication.utils.EncryptedSharedPreferencesUtil import kotlinx.coroutines.launch +import com.example.myapplication.network.BehaviorReporter class ForgetPasswordResetFragment : Fragment() { @@ -110,6 +111,11 @@ class ForgetPasswordResetFragment : Fragment() { } else if (password != confirmPassword) { Toast.makeText(activity, "The two password entries are inconsistent", Toast.LENGTH_SHORT).show() } else { + BehaviorReporter.report( + isNewUser = false, + "page_id" to "forgot_password_newpwd", + "element_id" to "next_btn", + ) loadingOverlay?.show() viewLifecycleOwner.lifecycleScope.launch { try { diff --git a/app/src/main/java/com/example/myapplication/ui/login/ForgetPasswordVerifyFragment.kt b/app/src/main/java/com/example/myapplication/ui/login/ForgetPasswordVerifyFragment.kt index b36050f..3d66bc6 100644 --- a/app/src/main/java/com/example/myapplication/ui/login/ForgetPasswordVerifyFragment.kt +++ b/app/src/main/java/com/example/myapplication/ui/login/ForgetPasswordVerifyFragment.kt @@ -21,6 +21,7 @@ import kotlinx.coroutines.launch import com.example.myapplication.network.RetrofitClient import com.example.myapplication.network.VerifyCodeRequest import android.util.Log +import com.example.myapplication.network.BehaviorReporter class ForgetPasswordVerifyFragment : Fragment() { @@ -59,6 +60,11 @@ class ForgetPasswordVerifyFragment : Fragment() { // 显示加载遮罩层 loadingOverlay?.show() + BehaviorReporter.report( + isNewUser = false, + "page_id" to "forgot_password_verify", + "element_id" to "next_btn", + ) viewLifecycleOwner.lifecycleScope.launch { try { val body = VerifyCodeRequest( diff --git a/app/src/main/java/com/example/myapplication/ui/login/LoginFragment.kt b/app/src/main/java/com/example/myapplication/ui/login/LoginFragment.kt index fba0d87..7bdfbc6 100644 --- a/app/src/main/java/com/example/myapplication/ui/login/LoginFragment.kt +++ b/app/src/main/java/com/example/myapplication/ui/login/LoginFragment.kt @@ -26,6 +26,7 @@ import com.example.myapplication.ui.mine.MineFragment import androidx.core.os.bundleOf import com.example.myapplication.network.AuthEventBus import com.example.myapplication.network.AuthEvent +import com.example.myapplication.network.BehaviorReporter class LoginFragment : Fragment() { @@ -57,10 +58,20 @@ class LoginFragment : Fragment() { // 注册 view.findViewById(R.id.tv_signup).setOnClickListener { + BehaviorReporter.report( + isNewUser = false, + "page_id" to "login", + "element_id" to "signup_btn", + ) findNavController().navigate(R.id.action_loginFragment_to_registerFragment) } // 忘记密码 view.findViewById(R.id.tv_forgot_password).setOnClickListener { + BehaviorReporter.report( + isNewUser = false, + "page_id" to "login", + "element_id" to "forgot_btn", + ) findNavController().navigate(R.id.action_loginFragment_to_forgetPasswordEmailFragment) } // 返回 - 在global_graph中,直接popBackStack回到globalEmptyFragment @@ -110,6 +121,12 @@ class LoginFragment : Fragment() { // 输入框不能为空 Toast.makeText(requireContext(), "The password and email address cannot be left empty!", Toast.LENGTH_SHORT).show() } else { + BehaviorReporter.report( + isNewUser = false, + "page_id" to "login_email", + "element_id" to "submit_btn", + ) + loadingOverlay?.show() // 调用登录API lifecycleScope.launch { @@ -125,6 +142,7 @@ class LoginFragment : Fragment() { EncryptedSharedPreferencesUtil.save(requireContext(), "email",email) // 触发登录成功事件,让MainActivity关闭全局overlay AuthEventBus.emit(AuthEvent.LoginSuccess) + AuthEventBus.emit(AuthEvent.CharacterDeleted(0)) // 不在这里popBackStack,让MainActivity的LoginSuccess事件处理关闭全局overlay } else { Toast.makeText(requireContext(), "Login failed: ${response.message}", Toast.LENGTH_SHORT).show() diff --git a/app/src/main/java/com/example/myapplication/ui/login/RegisterFragment.kt b/app/src/main/java/com/example/myapplication/ui/login/RegisterFragment.kt index b1eeb8c..cd00a1d 100644 --- a/app/src/main/java/com/example/myapplication/ui/login/RegisterFragment.kt +++ b/app/src/main/java/com/example/myapplication/ui/login/RegisterFragment.kt @@ -20,6 +20,7 @@ import com.example.myapplication.utils.EncryptedSharedPreferencesUtil import com.example.myapplication.ui.common.LoadingOverlay import kotlinx.coroutines.launch import android.util.Log +import com.example.myapplication.network.BehaviorReporter class RegisterFragment : Fragment() { @@ -113,6 +114,11 @@ class RegisterFragment : Fragment() { } else if (!isValidEmail(email)) { Toast.makeText(activity, "The email address format is incorrect", Toast.LENGTH_SHORT).show() } else { + BehaviorReporter.report( + isNewUser = false, + "page_id" to "register_email", + "element_id" to "submit_btn", + ) loadingOverlay?.show() viewLifecycleOwner.lifecycleScope.launch { try { diff --git a/app/src/main/java/com/example/myapplication/ui/login/RegisterVerifyFragment.kt b/app/src/main/java/com/example/myapplication/ui/login/RegisterVerifyFragment.kt index 830e9d1..c56a400 100644 --- a/app/src/main/java/com/example/myapplication/ui/login/RegisterVerifyFragment.kt +++ b/app/src/main/java/com/example/myapplication/ui/login/RegisterVerifyFragment.kt @@ -20,6 +20,7 @@ import com.example.myapplication.ui.common.CodeEditText import com.example.myapplication.utils.EncryptedSharedPreferencesUtil import kotlinx.coroutines.launch import android.widget.TextView +import com.example.myapplication.network.BehaviorReporter class RegisterVerifyFragment : Fragment() { @@ -73,7 +74,11 @@ class RegisterVerifyFragment : Fragment() { } loadingOverlay?.show() - + BehaviorReporter.report( + isNewUser = false, + "page_id" to "register_verify_email", + "element_id" to "confirm_btn", + ) viewLifecycleOwner.lifecycleScope.launch { try { val body = RegisterRequest( diff --git a/app/src/main/java/com/example/myapplication/ui/mine/MineFragment.kt b/app/src/main/java/com/example/myapplication/ui/mine/MineFragment.kt index fb0602f..ebb7d89 100644 --- a/app/src/main/java/com/example/myapplication/ui/mine/MineFragment.kt +++ b/app/src/main/java/com/example/myapplication/ui/mine/MineFragment.kt @@ -1,5 +1,8 @@ package com.example.myapplication.ui.mine +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context import android.os.Bundle import android.util.Log import android.view.LayoutInflater @@ -17,22 +20,30 @@ import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import androidx.navigation.NavController import androidx.navigation.fragment.findNavController +import com.bumptech.glide.Glide import com.example.myapplication.R import com.example.myapplication.network.AuthEvent import com.example.myapplication.network.AuthEventBus import com.example.myapplication.network.LoginResponse +import com.example.myapplication.network.ApiResponse +import com.example.myapplication.network.ShareResponse import com.example.myapplication.network.RetrofitClient +import com.example.myapplication.ui.common.LoadingOverlay import com.example.myapplication.utils.EncryptedSharedPreferencesUtil import de.hdodenhof.circleimageview.CircleImageView import kotlinx.coroutines.Job import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch +import com.example.myapplication.network.BehaviorReporter class MineFragment : Fragment() { private lateinit var nickname: TextView private lateinit var time: TextView private lateinit var logout: TextView + private lateinit var avatar: CircleImageView + private lateinit var share: LinearLayout + private lateinit var loadingOverlay: LoadingOverlay private var loadUserJob: Job? = null @@ -48,6 +59,7 @@ class MineFragment : Fragment() { override fun onDestroyView() { loadUserJob?.cancel() + loadingOverlay.remove() super.onDestroyView() } @@ -57,24 +69,44 @@ class MineFragment : Fragment() { nickname = view.findViewById(R.id.nickname) time = view.findViewById(R.id.time) logout = view.findViewById(R.id.logout) + avatar = view.findViewById(R.id.avatar) + share = view.findViewById(R.id.click_Share) + loadingOverlay = LoadingOverlay.attach(view.findViewById(R.id.rootCoordinator)) // 1) 先用本地缓存秒出首屏 renderFromCache() - // 2) 首次进入不刷新,由onResume处理 - - // // ✅ 手动刷新:不改布局也能用 - // // - 点昵称刷新 - // nickname.setOnClickListener { refreshUser(force = true, showToast = true) } - // // - 长按 time 刷新 - // time.setOnLongClickListener { - // refreshUser(force = true, showToast = true) - // true - // } + share.setOnClickListener { + viewLifecycleOwner.lifecycleScope.launch { + loadingOverlay.show() + try { + val response = getinviteCode() + response?.data?.h5Link?.let { link -> + val clipboard = requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + val clip = ClipData.newPlainText("h5Link", link) + clipboard.setPrimaryClip(clip) + Toast.makeText(context, "The sharing link has been copied to the clipboard.", Toast.LENGTH_LONG).show() + BehaviorReporter.report( + isNewUser = false, + "page_id" to "my", + "element_id" to "invite_copy", + ) + } + } finally { + loadingOverlay.hide() + } + } + } logout.setOnClickListener { LogoutDialogFragment { doLogout() } .show(parentFragmentManager, "logout_dialog") + + BehaviorReporter.report( + isNewUser = false, + "page_id" to "person_info", + "element_id" to "logout_btn", + ) } view.findViewById(R.id.imgLeft).setOnClickListener { @@ -85,17 +117,43 @@ class MineFragment : Fragment() { // 使用事件总线打开金币充值页面 AuthEventBus.emit(AuthEvent.OpenGlobalPage(R.id.goldCoinRechargeFragment)) } - view.findViewById(R.id.avatar).setOnClickListener { - safeNavigate(R.id.action_mineFragment_to_personalSettings) + avatar.setOnClickListener { + // 个人设置页面 + AuthEventBus.emit(AuthEvent.OpenGlobalPage(R.id.PersonalSettings)) } view.findViewById(R.id.keyboard_settings).setOnClickListener { - safeNavigate(R.id.action_mineFragment_to_mykeyboard) + // 我的键盘页面 + AuthEventBus.emit(AuthEvent.OpenGlobalPage(R.id.MyKeyboard)) + } + view.findViewById(R.id.click_record).setOnClickListener { + //消费记录 + AuthEventBus.emit(AuthEvent.OpenGlobalPage(R.id.consumptionRecordFragment)) + BehaviorReporter.report( + isNewUser = false, + "page_id" to "my", + "element_id" to "menu_item", + "item_title" to "消费记录" + ) } view.findViewById(R.id.click_Feedback).setOnClickListener { - safeNavigate(R.id.action_mineFragment_to_feedbackFragment) + //反馈 + AuthEventBus.emit(AuthEvent.OpenGlobalPage(R.id.feedbackFragment)) + BehaviorReporter.report( + isNewUser = false, + "page_id" to "my", + "element_id" to "menu_item", + "item_title" to "反馈" + ) } view.findViewById(R.id.click_Notice).setOnClickListener { - safeNavigate(R.id.action_mineFragment_to_notificationFragment) + //通知 + AuthEventBus.emit(AuthEvent.OpenGlobalPage(R.id.notificationFragment)) + BehaviorReporter.report( + isNewUser = false, + "page_id" to "my", + "element_id" to "menu_item", + "item_title" to "通知" + ) } // ✅ 监听登录成功/登出事件(跨 NavHost 可靠) @@ -108,6 +166,10 @@ class MineFragment : Fragment() { renderFromCache() refreshUser(force = true, showToast = false) } + AuthEvent.UserUpdated -> { + renderFromCache() + refreshUser(force = true, showToast = false) + } else -> Unit } } @@ -131,6 +193,11 @@ class MineFragment : Fragment() { ) nickname.text = cached?.nickName ?: "" time.text = cached?.vipExpiry?.let { "Due on November $it" } ?: "" + cached?.avatarUrl?.let { url -> + Glide.with(requireContext()) + .load(url) + .into(avatar) + } } /** @@ -154,15 +221,22 @@ class MineFragment : Fragment() { Log.d(TAG, "getUser ok: nick=${u?.nickName} vip=${u?.vipExpiry}") nickname.text = u?.nickName ?: "" + time.text = u?.vipExpiry?.let { "Due on November $it" } ?: "" + u?.avatarUrl?.let { url -> + Glide.with(requireContext()) + .load(url) + .into(avatar) + } + EncryptedSharedPreferencesUtil.save(requireContext(), "Personal_information", u) - if (showToast) Toast.makeText(requireContext(), "已刷新", Toast.LENGTH_SHORT).show() + if (showToast) Toast.makeText(requireContext(), "Refreshed", Toast.LENGTH_SHORT).show() } catch (e: Exception) { if (e is kotlinx.coroutines.CancellationException) return@launch Log.e(TAG, "getUser failed", e) - if (showToast && isAdded) Toast.makeText(requireContext(), "刷新失败", Toast.LENGTH_SHORT).show() + if (showToast && isAdded) Toast.makeText(requireContext(), "Refresh failed", Toast.LENGTH_SHORT).show() } } } @@ -180,6 +254,9 @@ class MineFragment : Fragment() { // 清空 UI nickname.text = "" time.text = "" + Glide.with(requireContext()) + .load(R.drawable.default_avatar) + .into(avatar) // 触发登出事件,让MainActivity打开登录页面 AuthEventBus.emit(AuthEvent.Logout(returnTabTag = "tab_mine")) @@ -209,4 +286,7 @@ class MineFragment : Fragment() { companion object { private const val TAG = "1314520-MineFragment" } + + private suspend fun getinviteCode(): ApiResponse? = + runCatching> { RetrofitClient.apiService.inviteCode() }.getOrNull() } diff --git a/app/src/main/java/com/example/myapplication/ui/mine/consumptionRecord/TransactionAdapter.kt b/app/src/main/java/com/example/myapplication/ui/mine/consumptionRecord/TransactionAdapter.kt new file mode 100644 index 0000000..0f6c51b --- /dev/null +++ b/app/src/main/java/com/example/myapplication/ui/mine/consumptionRecord/TransactionAdapter.kt @@ -0,0 +1,174 @@ +package com.example.myapplication.ui.mine.consumptionRecord + +import android.graphics.Color +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import android.widget.ProgressBar +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.example.myapplication.R +import com.example.myapplication.network.TransactionRecord + +class TransactionAdapter( + private val data: MutableList, + private val onCloseClick: () -> Unit, + private val onRechargeClick: () -> Unit +) : RecyclerView.Adapter() { + + companion object { + private const val TYPE_HEADER = 0 + private const val TYPE_ITEM = 1 + private const val TYPE_FOOTER = 2 + } + + // Header: balance + private var headerBalanceText: String = "0.00" + + // Footer state + private var showFooter: Boolean = false + private var footerNoMore: Boolean = false + + fun updateHeaderBalance(text: Any?) { + headerBalanceText = (text ?: "0.00").toString() + notifyItemChanged(0) + } + + fun setFooterLoading() { + showFooter = true + footerNoMore = false + notifyDataSetChanged() + } + + fun setFooterNoMore() { + showFooter = true + footerNoMore = true + notifyDataSetChanged() + } + + fun hideFooter() { + showFooter = false + footerNoMore = false + notifyDataSetChanged() + } + + fun replaceAll(list: List) { + data.clear() + data.addAll(list) + notifyDataSetChanged() + } + + fun append(list: List) { + if (list.isEmpty()) return + val start = 1 + data.size // header占1 + data.addAll(list) + notifyItemRangeInserted(start, list.size) + } + + override fun getItemCount(): Int { + // header + items + optional footer + return 1 + data.size + if (showFooter) 1 else 0 + } + + override fun getItemViewType(position: Int): Int { + return when { + position == 0 -> TYPE_HEADER + showFooter && position == itemCount - 1 -> TYPE_FOOTER + else -> TYPE_ITEM + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val inflater = LayoutInflater.from(parent.context) + return when (viewType) { + TYPE_HEADER -> { + val v = inflater.inflate(R.layout.layout_consumption_record_header, parent, false) + HeaderVH(v, onCloseClick, onRechargeClick) + } + TYPE_FOOTER -> { + val v = inflater.inflate(R.layout.item_loading_footer, parent, false) + FooterVH(v) + } + else -> { + val v = inflater.inflate(R.layout.item_transaction_record, parent, false) + ItemVH(v) + } + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (holder) { + is HeaderVH -> holder.bind(headerBalanceText) + is FooterVH -> holder.bind(footerNoMore) + is ItemVH -> holder.bind(data[position - 1]) // position-1 because header + } + } + + class HeaderVH( + itemView: View, + onCloseClick: () -> Unit, + onRechargeClick: () -> Unit + ) : RecyclerView.ViewHolder(itemView) { + + private val balance: TextView = itemView.findViewById(R.id.balance) + + init { + itemView.findViewById(R.id.iv_close).setOnClickListener { onCloseClick() } + itemView.findViewById(R.id.rechargeButton).setOnClickListener { onRechargeClick() } + } + + fun bind(balanceText: String) { + balance.text = balanceText + adjustBalanceTextSize(balance, balanceText) + } + + private fun adjustBalanceTextSize(tv: TextView, text: String) { + tv.textSize = when (text.length) { + in 0..3 -> 40f + 4 -> 36f + 5 -> 32f + 6 -> 28f + 7 -> 24f + 8 -> 22f + 9 -> 20f + else -> 16f + } + } + } + + class ItemVH(itemView: View) : RecyclerView.ViewHolder(itemView) { + private val tvTime: TextView = itemView.findViewById(R.id.tvTime) + private val tvDesc: TextView = itemView.findViewById(R.id.tvDesc) + private val tvAmount: TextView = itemView.findViewById(R.id.tvAmount) + + fun bind(item: TransactionRecord) { + tvTime.text = item.createdAt + tvDesc.text = item.description + tvAmount.text = "${item.amount}" + + // 根据type设置字体颜色 + val color = when (item.type) { + 1 -> Color.parseColor("#CD2853") // 收入 - 红色 + 2 -> Color.parseColor("#66CD7C") // 支出 - 绿色 + else -> tvAmount.currentTextColor // 保持当前颜色 + } + tvAmount.setTextColor(color) + } + } + + class FooterVH(itemView: View) : RecyclerView.ViewHolder(itemView) { + private val progress: ProgressBar = itemView.findViewById(R.id.progress) + private val tv: TextView = itemView.findViewById(R.id.tvLoading) + + fun bind(noMore: Boolean) { + if (noMore) { + progress.visibility = View.GONE + tv.text = "No more" + } else { + progress.visibility = View.VISIBLE + tv.text = "Loading..." + } + } + } +} diff --git a/app/src/main/java/com/example/myapplication/ui/mine/consumptionRecord/consumptionRecordFragment.kt b/app/src/main/java/com/example/myapplication/ui/mine/consumptionRecord/consumptionRecordFragment.kt new file mode 100644 index 0000000..d429110 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/ui/mine/consumptionRecord/consumptionRecordFragment.kt @@ -0,0 +1,181 @@ +package com.example.myapplication.ui.mine.consumptionRecord + +import android.app.Dialog +import android.content.DialogInterface +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.lifecycle.lifecycleScope +import androidx.navigation.fragment.findNavController +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout +import com.example.myapplication.R +import com.example.myapplication.network.ApiResponse +import com.example.myapplication.network.RetrofitClient +import com.example.myapplication.network.TransactionRecord +import com.example.myapplication.network.Wallet +import com.example.myapplication.network.transactionsRequest +import com.example.myapplication.network.transactionsResponse +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import com.example.myapplication.network.AuthEvent +import com.example.myapplication.network.AuthEventBus +import kotlinx.coroutines.launch + +class ConsumptionRecordFragment : BottomSheetDialogFragment() { + + private lateinit var swipeRefresh: SwipeRefreshLayout + private lateinit var rv: RecyclerView + private lateinit var adapter: TransactionAdapter + + private val listData = arrayListOf() + + private var pageNum = 1 + private val pageSize = 10 + private var totalPages = Int.MAX_VALUE + private var isLoading = false + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + return inflater.inflate(R.layout.fragment_consumption_record, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + swipeRefresh = view.findViewById(R.id.swipeRefresh) + rv = view.findViewById(R.id.rvTransactions) + + setupRecycler() + setupRefresh() + + refreshAll() + } + + /** + * ✅ 重点:关闭必须走 NavController popBackStack + * 不要 dismiss(),否则 global_nav 栈不会变,底部导航就会一直被隐藏 + */ + private fun closeByNav() { + runCatching { + findNavController().popBackStack() + }.onFailure { + // 万一不是走 nav 打开的(极少情况),再兜底 dismiss + dismissAllowingStateLoss() + } + } + + override fun onCancel(dialog: DialogInterface) { + super.onCancel(dialog) + // ✅ 用户手势下拉/点外部取消,也要 pop 返回栈 + closeByNav() + } + + override fun onDismiss(dialog: DialogInterface) { + super.onDismiss(dialog) + // ✅ 有些机型/场景只走 onDismiss,不走 onCancel,双保险 + closeByNav() + } + + private fun setupRecycler() { + adapter = TransactionAdapter( + data = listData, + onCloseClick = { closeByNav() }, // ✅ 改这里:不要 dismiss() + onRechargeClick = { + AuthEventBus.emit(AuthEvent.OpenGlobalPage(R.id.goldCoinRechargeFragment)) + } + ) + + rv.layoutManager = LinearLayoutManager(requireContext()) + rv.adapter = adapter + + rv.addOnScrollListener(object : RecyclerView.OnScrollListener() { + override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { + super.onScrollStateChanged(recyclerView, newState) + if (newState != RecyclerView.SCROLL_STATE_IDLE) return + + val reachedBottom = !recyclerView.canScrollVertically(1) + if (!reachedBottom) return + + if (!isLoading && pageNum < totalPages) { + loadMore() + } else if (!isLoading && pageNum >= totalPages) { + adapter.setFooterNoMore() + } + } + }) + } + + private fun setupRefresh() { + swipeRefresh.setOnRefreshListener { refreshAll() } + } + + private fun refreshAll() { + lifecycleScope.launch { + swipeRefresh.isRefreshing = true + + pageNum = 1 + totalPages = Int.MAX_VALUE + isLoading = false + + adapter.hideFooter() + adapter.replaceAll(emptyList()) + + val walletResp = getwalletBalance() + val balanceText = walletResp?.data?.balanceDisplay ?: "0.00" + adapter.updateHeaderBalance(balanceText) + + loadPage(targetPage = 1, isRefresh = true) + + swipeRefresh.isRefreshing = false + } + } + + private fun loadMore() { + lifecycleScope.launch { loadPage(targetPage = pageNum + 1, isRefresh = false) } + } + + private suspend fun loadPage(targetPage: Int, isRefresh: Boolean) { + if (isLoading) return + isLoading = true + + if (!isRefresh) adapter.setFooterLoading() + + val body = transactionsRequest(pageNum = targetPage, pageSize = pageSize) + val resp = gettransactions(body) + val data = resp?.data + + if (data != null) { + totalPages = data.pages + pageNum = data.current + + val records = data.records + + if (isRefresh) adapter.replaceAll(records) else adapter.append(records) + + if (pageNum >= totalPages) adapter.setFooterNoMore() else adapter.hideFooter() + + rv.post { + val notScrollableYet = !rv.canScrollVertically(1) + if (!isLoading && notScrollableYet && pageNum < totalPages) { + loadMore() + } + } + } else { + adapter.hideFooter() + } + + isLoading = false + } + + // ========================网络请求=========================================== + private suspend fun getwalletBalance(): ApiResponse? = + runCatching { RetrofitClient.apiService.walletBalance() }.getOrNull() + + private suspend fun gettransactions(body: transactionsRequest): ApiResponse? = + runCatching { RetrofitClient.apiService.transactions(body) }.getOrNull() +} diff --git a/app/src/main/java/com/example/myapplication/ui/mine/myotherpages/FeedbackFragment.kt b/app/src/main/java/com/example/myapplication/ui/mine/myotherpages/FeedbackFragment.kt index 8579ba6..78d5f09 100644 --- a/app/src/main/java/com/example/myapplication/ui/mine/myotherpages/FeedbackFragment.kt +++ b/app/src/main/java/com/example/myapplication/ui/mine/myotherpages/FeedbackFragment.kt @@ -2,19 +2,24 @@ package com.example.myapplication.ui.mine.myotherpages import android.os.Bundle import android.view.LayoutInflater +import android.view.MotionEvent import android.view.View import android.view.ViewGroup -import androidx.core.content.ContextCompat -import androidx.fragment.app.Fragment -import com.example.myapplication.R import android.widget.FrameLayout -import com.google.android.material.bottomsheet.BottomSheetDialogFragment -import com.google.android.material.button.MaterialButton -import com.google.android.material.textfield.TextInputLayout -import java.util.* +import android.widget.Toast +import androidx.fragment.app.Fragment +import com.example.myapplication.R +import com.example.myapplication.network.ApiResponse +import com.example.myapplication.network.RetrofitClient +import com.example.myapplication.network.feedbackRequest +import com.google.android.material.textfield.TextInputEditText +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import com.example.myapplication.network.BehaviorReporter class FeedbackFragment : Fragment() { - + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -26,9 +31,56 @@ class FeedbackFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - // 设置关闭按钮点击事件 + // 关闭按钮 view.findViewById(R.id.iv_close).setOnClickListener { parentFragmentManager.popBackStack() } + + // 让多行输入框:不聚焦也能上下滑动内容 + val etFeedback = view.findViewById(R.id.et_feedback) + etFeedback.apply { + // 可选:让它本身可滚动(你 XML 已经写了也没问题) + isVerticalScrollBarEnabled = true + + setOnTouchListener { v, event -> + // 告诉父布局(NestedScrollView)先别抢这个触摸事件 + v.parent?.requestDisallowInterceptTouchEvent(true) + + // 手指抬起/取消时,把控制权还给父布局(页面还能继续滚) + if (event.action == MotionEvent.ACTION_UP || event.action == MotionEvent.ACTION_CANCEL) { + v.parent?.requestDisallowInterceptTouchEvent(false) + } + + // false:不吞事件,让 EditText 自己处理滚动/光标 + false + } + } + + // 提交反馈按钮点击事件 + view.findViewById(R.id.btn_keyboard).setOnClickListener { + val feedbackText = etFeedback.text.toString().trim() + if (feedbackText.isEmpty()) { + Toast.makeText(context, "Please enter your feedback", Toast.LENGTH_SHORT).show() + return@setOnClickListener + } + BehaviorReporter.report( + isNewUser = false, + "page_id" to "feedback", + "element_id" to "commit_btn", + "content" to feedbackText + ) + CoroutineScope(Dispatchers.Main).launch { + val response = submitFeedback(feedbackRequest(content = feedbackText)) + if (response?.code == 0) { + Toast.makeText(context, "Feedback submitted successfully", Toast.LENGTH_SHORT).show() + parentFragmentManager.popBackStack() + } else { + Toast.makeText(context, "Failed to submit feedback", Toast.LENGTH_SHORT).show() + } + } + } } -} \ No newline at end of file + //提交反馈 + private suspend fun submitFeedback(body:feedbackRequest): ApiResponse? = + runCatching> { RetrofitClient.apiService.feedback(body) }.getOrNull() +} diff --git a/app/src/main/java/com/example/myapplication/ui/mine/myotherpages/GenderSelectSheet.kt b/app/src/main/java/com/example/myapplication/ui/mine/myotherpages/GenderSelectSheet.kt new file mode 100644 index 0000000..abc3f16 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/ui/mine/myotherpages/GenderSelectSheet.kt @@ -0,0 +1,165 @@ +package com.example.myapplication.ui.mine.myotherpages + +import android.graphics.Color +import android.os.Bundle +import android.view.Gravity +import android.view.LayoutInflater +import android.view.MotionEvent +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.LinearSnapHelper +import androidx.recyclerview.widget.RecyclerView +import com.example.myapplication.R +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import kotlin.math.abs + +class GenderSelectSheet : BottomSheetDialogFragment() { + + private val values = listOf("Male", "Female", "The third gender") + private var selectedIndex = 0 + + private val itemHeightDp = 48f // 每行高度(和 Adapter 里一致) + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + val view = inflater.inflate(R.layout.sheet_select_gender, container, false) + + selectedIndex = (arguments?.getInt(ARG_INITIAL) ?: 0).coerceIn(0, values.lastIndex) + + val rv = view.findViewById(R.id.gender_wheel) + + val layoutManager = LinearLayoutManager(requireContext(), RecyclerView.VERTICAL, false) + rv.layoutManager = layoutManager + rv.adapter = WheelAdapter(values, dpToPx(itemHeightDp)) + rv.overScrollMode = View.OVER_SCROLL_NEVER + rv.isNestedScrollingEnabled = true + rv.clipToPadding = false + + val snapHelper = LinearSnapHelper() + snapHelper.attachToRecyclerView(rv) + + // ✅ 关键:触摸滚轮时不让 BottomSheet 抢手势(否则会拖动弹窗) + rv.setOnTouchListener { v, event -> + when (event.actionMasked) { + MotionEvent.ACTION_DOWN -> { + setSheetDraggable(false) + v.parent?.requestDisallowInterceptTouchEvent(true) + } + MotionEvent.ACTION_MOVE -> v.parent?.requestDisallowInterceptTouchEvent(true) + MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { + setSheetDraggable(true) + v.parent?.requestDisallowInterceptTouchEvent(false) + } + } + false + } + + rv.addOnScrollListener(object : RecyclerView.OnScrollListener() { + override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { + updateChildColors(recyclerView) + } + + override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { + if (newState == RecyclerView.SCROLL_STATE_IDLE) { + val snapView = snapHelper.findSnapView(layoutManager) ?: return + val pos = layoutManager.getPosition(snapView).coerceIn(0, values.lastIndex) + selectedIndex = pos + updateChildColors(recyclerView) + } + } + }) + + // ✅ 关键:给上下 padding,让 3 条内容也能滚动/居中吸附 + rv.post { + val itemPx = dpToPx(itemHeightDp) + val pad = (rv.height / 2 - itemPx / 2).coerceAtLeast(0) + rv.setPadding(rv.paddingLeft, pad, rv.paddingRight, pad) + + layoutManager.scrollToPositionWithOffset(selectedIndex, pad) + rv.post { updateChildColors(rv) } + } + + view.findViewById(R.id.btn_close).setOnClickListener { dismiss() } + + view.findViewById(R.id.btn_save).setOnClickListener { + parentFragmentManager.setFragmentResult( + REQ_KEY, + Bundle().apply { putInt(BUNDLE_KEY_GENDER, selectedIndex) } + ) + dismiss() + } + + return view + } + + private fun setSheetDraggable(draggable: Boolean) { + val d = dialog as? BottomSheetDialog ?: return + val sheet = d.findViewById(com.google.android.material.R.id.design_bottom_sheet) ?: return + BottomSheetBehavior.from(sheet).isDraggable = draggable + } + + /** 根据距离中心点决定文字颜色 */ + private fun updateChildColors(rv: RecyclerView) { + val centerY = rv.height / 2f + val selectedColor = Color.parseColor("#02BEAC") + val normalColor = Color.parseColor("#B5B5B5") + + for (i in 0 until rv.childCount) { + val child = rv.getChildAt(i) + val tv = child as? TextView ?: continue + + val childCenterY = (child.top + child.bottom) / 2f + val distance = abs(childCenterY - centerY) + + val isSelected = distance < dpToPx(8f) + tv.setTextColor(if (isSelected) selectedColor else normalColor) + } + } + + companion object { + const val REQ_KEY = "req_select_gender" + const val BUNDLE_KEY_GENDER = "bundle_gender" + private const val ARG_INITIAL = "arg_initial_gender" + + fun newInstance(initialGender: Int) = GenderSelectSheet().apply { + arguments = Bundle().apply { putInt(ARG_INITIAL, initialGender) } + } + } + + private fun dpToPx(dp: Float): Int = + (dp * resources.displayMetrics.density + 0.5f).toInt() + + private class WheelAdapter( + private val items: List, + private val itemHeightPx: Int + ) : RecyclerView.Adapter() { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH { + val tv = TextView(parent.context).apply { + layoutParams = RecyclerView.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + itemHeightPx + ) + gravity = Gravity.CENTER + textSize = 18f + setTextColor(Color.parseColor("#B5B5B5")) + } + return VH(tv) + } + + override fun onBindViewHolder(holder: VH, position: Int) { + (holder.itemView as TextView).text = items[position] + } + + override fun getItemCount(): Int = items.size + + class VH(itemView: View) : RecyclerView.ViewHolder(itemView) + } +} diff --git a/app/src/main/java/com/example/myapplication/ui/mine/myotherpages/NicknameEditSheet.kt b/app/src/main/java/com/example/myapplication/ui/mine/myotherpages/NicknameEditSheet.kt new file mode 100644 index 0000000..dfa9147 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/ui/mine/myotherpages/NicknameEditSheet.kt @@ -0,0 +1,64 @@ +package com.example.myapplication.ui.mine.myotherpages + +import android.os.Bundle +import android.text.InputType +import android.view.Gravity +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.LinearLayout +import android.widget.TextView +import android.widget.Toast +import androidx.core.view.setPadding +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import com.google.android.material.button.MaterialButton +import com.google.android.material.textfield.TextInputEditText +import com.google.android.material.textfield.TextInputLayout +import com.example.myapplication.R + +class NicknameEditSheet : BottomSheetDialogFragment() { + + override fun onStart() { + super.onStart() + dialog?.window?.setSoftInputMode( + android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE or + android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE + ) + } + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + val view = inflater.inflate(R.layout.sheet_edit_nickname, container, false) + val initial = arguments?.getString(ARG_INITIAL).orEmpty() + + val et = view.findViewById(R.id.et_nickname) + et.setText(initial) + + view.findViewById(R.id.btn_close).setOnClickListener { dismiss() } + + view.findViewById(R.id.btn_save).setOnClickListener { + val nickname = et.text?.toString()?.trim().orEmpty() + if (nickname.isBlank()) return@setOnClickListener + + parentFragmentManager.setFragmentResult( + REQ_KEY, + Bundle().apply { putString(BUNDLE_KEY_NICKNAME, nickname) } + ) + dismiss() + } + + return view + } + + companion object { + const val REQ_KEY = "req_edit_nickname" + const val BUNDLE_KEY_NICKNAME = "bundle_nickname" + private const val ARG_INITIAL = "arg_initial_nickname" + + fun newInstance(initial: String) = NicknameEditSheet().apply { + arguments = Bundle().apply { putString(ARG_INITIAL, initial) } + } + } +} diff --git a/app/src/main/java/com/example/myapplication/ui/mine/myotherpages/PersonalSettings.kt b/app/src/main/java/com/example/myapplication/ui/mine/myotherpages/PersonalSettings.kt index 2865dc2..74aea1c 100644 --- a/app/src/main/java/com/example/myapplication/ui/mine/myotherpages/PersonalSettings.kt +++ b/app/src/main/java/com/example/myapplication/ui/mine/myotherpages/PersonalSettings.kt @@ -1,34 +1,411 @@ package com.example.myapplication.ui.mine.myotherpages +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context +import android.content.pm.PackageManager +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.net.Uri import android.os.Bundle +import android.os.Environment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.core.content.ContextCompat -import androidx.fragment.app.Fragment -import com.example.myapplication.R import android.widget.FrameLayout +import android.widget.TextView +import android.widget.Toast +import androidx.activity.result.PickVisualMediaRequest +import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.content.ContextCompat +import androidx.core.content.FileProvider +import androidx.lifecycle.lifecycleScope +import com.bumptech.glide.Glide +import com.example.myapplication.R +import com.example.myapplication.network.ApiResponse +import com.example.myapplication.network.AuthEvent +import com.example.myapplication.network.AuthEventBus +import com.example.myapplication.network.BehaviorReporter +import com.example.myapplication.network.RetrofitClient +import com.example.myapplication.network.User +import com.example.myapplication.network.updateInfoRequest +import com.example.myapplication.ui.common.LoadingOverlay import com.google.android.material.bottomsheet.BottomSheetDialogFragment -import com.google.android.material.button.MaterialButton -import com.google.android.material.textfield.TextInputLayout -import java.util.* +import de.hdodenhof.circleimageview.CircleImageView +import kotlinx.coroutines.launch +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.MultipartBody +import okhttp3.RequestBody +import java.io.File +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale +import android.util.Log class PersonalSettings : BottomSheetDialogFragment() { - + + private var user: User? = null + + private lateinit var avatar: CircleImageView + private lateinit var tvNickname: TextView + private lateinit var tvGender: TextView + private lateinit var tvUserId: TextView + private lateinit var loadingOverlay: LoadingOverlay + + /** + * ✅ Android Photo Picker + * - Android 13+:系统原生 Photo Picker + * - 低版本:会自动 fallback 到系统/兼容实现 + * - 不需要 READ/WRITE_EXTERNAL_STORAGE + */ + private val galleryLauncher = + registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri: Uri? -> + uri?.let { handleImageResult(it) } + } + + private val cameraLauncher = + registerForActivityResult(ActivityResultContracts.TakePicture()) { success: Boolean -> + if (success) { + cameraImageUri?.let { handleImageResult(it) } + } + } + + private var cameraImageUri: Uri? = null + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ): View? { - return inflater.inflate(R.layout.personal_settings, container, false) - } + ): View = inflater.inflate(R.layout.personal_settings, container, false) override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - // 设置关闭按钮点击事件 + // 初始化loadingOverlay + loadingOverlay = LoadingOverlay.attach(requireView() as ViewGroup) + + // 关闭 view.findViewById(R.id.iv_close).setOnClickListener { - parentFragmentManager.popBackStack() + AuthEventBus.emit(AuthEvent.UserUpdated) + requireActivity().onBackPressedDispatcher.onBackPressed() + } + + // bind + avatar = view.findViewById(R.id.avatar) + tvNickname = view.findViewById(R.id.tv_nickname_value) + tvGender = view.findViewById(R.id.tv_gender_value) + tvUserId = view.findViewById(R.id.tv_userid_value) + + // Avatar click listener + avatar.setOnClickListener { + showImagePickerDialog() + } + + // ===================== FragmentResult listeners ===================== + + // 昵称保存回传 + parentFragmentManager.setFragmentResultListener( + NicknameEditSheet.REQ_KEY, + viewLifecycleOwner + ) { _, bundle -> + val newName = bundle.getString(NicknameEditSheet.BUNDLE_KEY_NICKNAME).orEmpty() + if (newName.isBlank()) return@setFragmentResultListener + lifecycleScope.launch { + loadingOverlay.show() + try { + val returnValue = setupdateUserInfo(updateInfoRequest(nickName = newName)) + Log.d("PersonalSettings", "setupdateUserInfo: $returnValue") + if (returnValue?.code == 0) { + tvNickname.text = newName + } + user = user?.copy(nickName = newName) + } catch (e: Exception) { + Log.e("PersonalSettings", "Failed to update nickname", e) + } finally { + loadingOverlay.hide() + } + } + } + + // 性别保存回传 + parentFragmentManager.setFragmentResultListener( + GenderSelectSheet.REQ_KEY, + viewLifecycleOwner + ) { _, bundle -> + val newGender = bundle.getInt(GenderSelectSheet.BUNDLE_KEY_GENDER, 0) + + lifecycleScope.launch { + loadingOverlay.show() + try { + val returnValue = setupdateUserInfo(updateInfoRequest(gender = newGender)) + if (returnValue?.code == 0) { + tvGender.text = genderText(newGender) + } + user = user?.copy(gender = newGender) + } catch (e: Exception) { + Log.e("PersonalSettings", "Failed to update gender", e) + } finally { + loadingOverlay.hide() + } + } + } + + // ===================== row click ===================== + + // Nickname:打开编辑 BottomSheet(arguments 传初始值) + view.findViewById(R.id.row_nickname).setOnClickListener { + NicknameEditSheet.newInstance(user?.nickName.orEmpty()) + .show(parentFragmentManager, "NicknameEditSheet") + } + + // Gender:打开选择 BottomSheet + view.findViewById(R.id.row_gender).setOnClickListener { + GenderSelectSheet.newInstance(user?.gender ?: 0) + .show(parentFragmentManager, "GenderSelectSheet") + } + + // UserID:点击复制 + view.findViewById(R.id.row_userid).setOnClickListener { + val uid = user?.uid?.toString() ?: tvUserId.text?.toString().orEmpty() + if (uid.isBlank()) return@setOnClickListener + copyToClipboard(uid) + Toast.makeText(requireContext(), "Copy successfully", Toast.LENGTH_SHORT).show() + } + + // ===================== load & render ===================== + + viewLifecycleOwner.lifecycleScope.launch { + loadingOverlay.show() + try { + val resp = getUserdata() + val u = resp?.data // 如果你的 ApiResponse 字段不是 data,这里改成你的字段名 + if (u == null) { + Toast.makeText(requireContext(), "Load failed", Toast.LENGTH_SHORT).show() + return@launch + } + user = u + renderUser(u) + } finally { + loadingOverlay.hide() + } } } -} \ No newline at end of file + + private fun renderUser(u: User) { + tvNickname.text = u.nickName + tvGender.text = genderText(u.gender) + tvUserId.text = u.uid.toString() + + Glide.with(this) + .load(u.avatarUrl) + .placeholder(R.drawable.default_avatar) + .error(R.drawable.default_avatar) + .into(avatar) + } + + private fun genderText(gender: Int): String = when (gender) { + 1 -> "Female" + 2 -> "The third gender" + 0 -> "Male" + else -> "" + } + + private fun copyToClipboard(text: String) { + val cm = requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + cm.setPrimaryClip(ClipData.newPlainText("user_id", text)) + } + + private suspend fun getUserdata(): ApiResponse? = + runCatching { RetrofitClient.apiService.getUser() }.getOrNull() + + private suspend fun setupdateUserInfo(body: updateInfoRequest): ApiResponse? = + runCatching { RetrofitClient.apiService.updateUserInfo(body) }.getOrNull() + + private val cameraPermissionLauncher = registerForActivityResult( + ActivityResultContracts.RequestPermission() + ) { isGranted -> + if (isGranted) { + cameraImageUri = createImageFile() + cameraImageUri?.let { cameraLauncher.launch(it) } + } else { + Toast.makeText( + requireContext(), + "Camera permission is required to take photos", + Toast.LENGTH_SHORT + ).show() + } + } + + private fun showImagePickerDialog() { + val options = arrayOf( + getString(R.string.choose_from_gallery), + getString(R.string.take_photo) + ) + + androidx.appcompat.app.AlertDialog.Builder(requireContext()) + .setTitle(R.string.change_avatar) + .setItems(options) { _, which -> + when (which) { + 0 -> { + // ✅ Photo Picker: ImageOnly + galleryLauncher.launch( + PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly) + ) + } + 1 -> { + if (ContextCompat.checkSelfPermission( + requireContext(), + android.Manifest.permission.CAMERA + ) == PackageManager.PERMISSION_GRANTED + ) { + cameraImageUri = createImageFile() + cameraImageUri?.let { cameraLauncher.launch(it) } + } else { + cameraPermissionLauncher.launch(android.Manifest.permission.CAMERA) + } + } + } + } + .setNegativeButton(R.string.cancel, null) + .show() + } + + private fun handleImageResult(uri: Uri) { + Glide.with(this) + .load(uri) + .into(avatar) + + lifecycleScope.launch { + uploadAvatar(uri) + } + } + + private suspend fun uploadAvatar(uri: Uri) { + BehaviorReporter.report( + isNewUser = false, + "page_id" to "person_info", + "element_id" to "avatar_edit", + ) + + loadingOverlay.show() + try { + val resolver = requireContext().contentResolver + + // Get MIME type to determine image format + val mimeType = resolver.getType(uri) + val isPng = mimeType?.equals("image/png", ignoreCase = true) == true + + // Determine file extension and media type based on MIME type + val fileExtension = if (isPng) ".png" else ".jpg" + val mediaType = if (isPng) "image/png" else "image/jpeg" + + // Temp file in app-private external files dir (no storage permission needed) + val storageDir = requireContext().getExternalFilesDir(Environment.DIRECTORY_PICTURES) + val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) + val tempFile = File.createTempFile("UPLOAD_${timeStamp}_", fileExtension, storageDir) + + // 先读取尺寸信息(inJustDecodeBounds) + val boundsOptions = BitmapFactory.Options().apply { inJustDecodeBounds = true } + resolver.openInputStream(uri)?.use { ins -> + BitmapFactory.decodeStream(ins, null, boundsOptions) + } ?: return + + // Calculate inSampleSize (粗略控制内存) + var inSampleSize = 1 + val maxSizeBytes = 5 * 1024 * 1024 // 5MB 目标 + // 简单估算:w*h*4 + if (boundsOptions.outHeight > 0 && boundsOptions.outWidth > 0) { + val estimated = boundsOptions.outHeight.toLong() * boundsOptions.outWidth.toLong() * 4L + if (estimated > maxSizeBytes) { + val halfHeight = boundsOptions.outHeight / 2 + val halfWidth = boundsOptions.outWidth / 2 + while (halfHeight / inSampleSize >= 1024 && halfWidth / inSampleSize >= 1024) { + inSampleSize *= 2 + } + } + } + + // Decode bitmap with inSampleSize + val decodeOptions = BitmapFactory.Options().apply { + inJustDecodeBounds = false + this.inSampleSize = inSampleSize + } + + val bitmap = resolver.openInputStream(uri)?.use { ins -> + BitmapFactory.decodeStream(ins, null, decodeOptions) + } + + if (bitmap == null) { + Toast.makeText(requireContext(), "Failed to decode image", Toast.LENGTH_SHORT).show() + return + } + + // Compress to tempFile + // 注意:用 outputStream 反复 compress 时不要在同一个 stream 上 truncate, + // 最稳是每次重新打开 stream 写入。 + if (isPng) { + tempFile.outputStream().use { out -> + bitmap.compress(Bitmap.CompressFormat.PNG, 100, out) + } + } else { + var quality = 90 + do { + tempFile.outputStream().use { out -> + bitmap.compress(Bitmap.CompressFormat.JPEG, quality, out) + } + quality -= 10 + } while (tempFile.length() > maxSizeBytes && quality > 10) + } + + if (tempFile.length() > maxSizeBytes) { + Toast.makeText(requireContext(), "Image is too large after compression", Toast.LENGTH_SHORT).show() + bitmap.recycle() + tempFile.delete() + return + } + + val requestFile: RequestBody = RequestBody.create(mediaType.toMediaTypeOrNull(), tempFile) + val body = MultipartBody.Part.createFormData("file", tempFile.name, requestFile) + + val response = RetrofitClient.createFileUploadService() + .uploadFile("avatar", body) + + // Clean up + bitmap.recycle() + tempFile.delete() + + if (response?.code == 0) { + setupdateUserInfo(updateInfoRequest(avatarUrl = response.data)) + Toast.makeText(requireContext(), R.string.avatar_updated, Toast.LENGTH_SHORT).show() + user = user?.copy(avatarUrl = response.data) + } else { + Toast.makeText(requireContext(), R.string.upload_failed, Toast.LENGTH_SHORT).show() + } + } catch (e: Exception) { + Toast.makeText(requireContext(), R.string.upload_error, Toast.LENGTH_SHORT).show() + Log.e("PersonalSettings", "Upload avatar error", e) + } finally { + loadingOverlay.hide() + } + } + + private fun createImageFile(): Uri? { + val storageDir = requireContext().getExternalFilesDir(Environment.DIRECTORY_PICTURES) + val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) + val imageFile = File.createTempFile( + "JPEG_${timeStamp}_", + ".jpg", + storageDir + ) + return FileProvider.getUriForFile( + requireContext(), + "${requireContext().packageName}.fileprovider", + imageFile + ) + } + + override fun onDestroyView() { + loadingOverlay.remove() + super.onDestroyView() + } +} diff --git a/app/src/main/java/com/example/myapplication/ui/shop/ShopFragment.kt b/app/src/main/java/com/example/myapplication/ui/shop/ShopFragment.kt index 0b14ad5..b768366 100644 --- a/app/src/main/java/com/example/myapplication/ui/shop/ShopFragment.kt +++ b/app/src/main/java/com/example/myapplication/ui/shop/ShopFragment.kt @@ -29,6 +29,7 @@ import com.google.android.material.appbar.AppBarLayout import kotlinx.coroutines.Job import kotlinx.coroutines.launch import java.util.concurrent.atomic.AtomicBoolean +import com.example.myapplication.network.BehaviorReporter class ShopFragment : Fragment(R.layout.fragment_shop) { @@ -239,6 +240,10 @@ class ShopFragment : Fragment(R.layout.fragment_shop) { } } } + BehaviorReporter.report( + isNewUser = false, + "page_id" to "shop_item_list", + ) } } @@ -400,10 +405,22 @@ class ShopFragment : Fragment(R.layout.fragment_shop) { AuthEventBus.emit(AuthEvent.OpenGlobalPage(R.id.goldCoinRechargeFragment)) } view.findViewById(R.id.skinButton).setOnClickListener { + // 使用事件总线打开我的皮肤页面 findNavController().navigate(R.id.action_shopfragment_to_myskin) + BehaviorReporter.report( + isNewUser = false, + "page_id" to "shop", + "element_id" to "my_skin_btn", + ) } view.findViewById(R.id.searchButton).setOnClickListener { + // 使用事件总线打开搜索页面 findNavController().navigate(R.id.action_shopfragment_to_searchfragment) + BehaviorReporter.report( + isNewUser = false, + "page_id" to "shop", + "element_id" to "search_btn", + ) } } diff --git a/app/src/main/java/com/example/myapplication/ui/shop/ThemeCardAdapter.kt b/app/src/main/java/com/example/myapplication/ui/shop/ThemeCardAdapter.kt index 4aafab6..4b5ba6d 100644 --- a/app/src/main/java/com/example/myapplication/ui/shop/ThemeCardAdapter.kt +++ b/app/src/main/java/com/example/myapplication/ui/shop/ThemeCardAdapter.kt @@ -17,6 +17,7 @@ import com.example.myapplication.network.AuthEvent import com.example.myapplication.network.AuthEventBus import com.example.myapplication.network.themeStyle import com.google.android.material.card.MaterialCardView +import com.example.myapplication.network.BehaviorReporter class ThemeCardAdapter : ListAdapter(DiffCallback) { @@ -56,6 +57,11 @@ class ThemeCardAdapter : ListAdapter Unit, @@ -94,11 +96,18 @@ class MySkinAdapter( } holder.itemView.setOnClickListener { + if (editMode) { if (selected) selectedIds.remove(item.id) else selectedIds.add(item.id) onSelectionChanged(selectedIds.size) notifyItemChanged(position) } else { + BehaviorReporter.report( + isNewUser = false, + "page_id" to "my_skin", + "element_id" to "theme_card", + "theme_id" to item.id + ) // 跳转到主题详情页面 val bundle = Bundle().apply { putInt("themeId", item.id) diff --git a/app/src/main/java/com/example/myapplication/ui/shop/search/SearchFragment.kt b/app/src/main/java/com/example/myapplication/ui/shop/search/SearchFragment.kt index 00f6959..f36a359 100644 --- a/app/src/main/java/com/example/myapplication/ui/shop/search/SearchFragment.kt +++ b/app/src/main/java/com/example/myapplication/ui/shop/search/SearchFragment.kt @@ -23,6 +23,7 @@ import com.example.myapplication.ui.shop.ThemeCardAdapter import com.google.android.flexbox.FlexboxLayout import com.google.android.flexbox.FlexboxLayout.LayoutParams import kotlinx.coroutines.launch +import com.example.myapplication.network.BehaviorReporter @@ -86,7 +87,13 @@ class SearchFragment : Fragment() { // 把搜索词放进 Bundle val bundle = bundleOf("search_keyword" to keyword) - + + BehaviorReporter.report( + isNewUser = false, + "page_id" to "search", + "element_id" to "search_submit", + "keyword" to bundle, + ) // 跳转时带上 bundle findNavController().navigate( R.id.action_searchFragment_to_searchResultFragment, @@ -102,6 +109,10 @@ class SearchFragment : Fragment() { // 清空历史记录 view.findViewById(R.id.iv_delete_history).setOnClickListener { clearHistory() + BehaviorReporter.report( + isNewUser = false, + "page_id" to "search", + ) } } @@ -155,8 +166,13 @@ class SearchFragment : Fragment() { tv.setOnClickListener { etInput.setText(keyword) etInput.setSelection(keyword.length) + BehaviorReporter.report( + isNewUser = false, + "page_id" to "search", + "element_id" to "history_item", + "keyword" to keyword, + ) } - return tv } diff --git a/app/src/main/res/drawable/ai_caard_bg.xml b/app/src/main/res/drawable/ai_caard_bg.xml new file mode 100644 index 0000000..9ee15f6 --- /dev/null +++ b/app/src/main/res/drawable/ai_caard_bg.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/associate_close.png b/app/src/main/res/drawable/associate_close.png new file mode 100644 index 0000000..d3c9809 Binary files /dev/null and b/app/src/main/res/drawable/associate_close.png differ diff --git a/app/src/main/res/drawable/complete_close_bg.xml b/app/src/main/res/drawable/complete_close_bg.xml new file mode 100644 index 0000000..c8b23d0 --- /dev/null +++ b/app/src/main/res/drawable/complete_close_bg.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/consumption_details_bg.xml b/app/src/main/res/drawable/consumption_details_bg.xml new file mode 100644 index 0000000..445361e --- /dev/null +++ b/app/src/main/res/drawable/consumption_details_bg.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/first_add.png b/app/src/main/res/drawable/first_add.png new file mode 100644 index 0000000..99249b9 Binary files /dev/null and b/app/src/main/res/drawable/first_add.png differ diff --git a/app/src/main/res/drawable/gender_background.xml b/app/src/main/res/drawable/gender_background.xml index 4420fb3..47a483c 100644 --- a/app/src/main/res/drawable/gender_background.xml +++ b/app/src/main/res/drawable/gender_background.xml @@ -2,4 +2,5 @@ + \ No newline at end of file diff --git a/app/src/main/res/drawable/gender_background_select.xml b/app/src/main/res/drawable/gender_background_select.xml new file mode 100644 index 0000000..c5996f6 --- /dev/null +++ b/app/src/main/res/drawable/gender_background_select.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/gold_coin_bg.xml b/app/src/main/res/drawable/gold_coin_bg.xml new file mode 100644 index 0000000..b326e83 --- /dev/null +++ b/app/src/main/res/drawable/gold_coin_bg.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/keyboard_button_bg4.xml b/app/src/main/res/drawable/keyboard_button_bg4.xml index 235ad94..087d556 100644 --- a/app/src/main/res/drawable/keyboard_button_bg4.xml +++ b/app/src/main/res/drawable/keyboard_button_bg4.xml @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/app/src/main/res/drawable/list_two_bg_already.xml b/app/src/main/res/drawable/list_two_bg_already.xml new file mode 100644 index 0000000..37d8946 --- /dev/null +++ b/app/src/main/res/drawable/list_two_bg_already.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/my_keyboard_cancel.xml b/app/src/main/res/drawable/my_keyboard_cancel.xml new file mode 100644 index 0000000..16990a2 --- /dev/null +++ b/app/src/main/res/drawable/my_keyboard_cancel.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/operation_add.png b/app/src/main/res/drawable/operation_add.png new file mode 100644 index 0000000..52a253d Binary files /dev/null and b/app/src/main/res/drawable/operation_add.png differ diff --git a/app/src/main/res/drawable/pop_collapse.png b/app/src/main/res/drawable/pop_collapse.png new file mode 100644 index 0000000..1a580a9 Binary files /dev/null and b/app/src/main/res/drawable/pop_collapse.png differ diff --git a/app/src/main/res/drawable/record.png b/app/src/main/res/drawable/record.png new file mode 100644 index 0000000..c92289f Binary files /dev/null and b/app/src/main/res/drawable/record.png differ diff --git a/app/src/main/res/drawable/round_bg_others_add.png b/app/src/main/res/drawable/round_bg_others_add.png new file mode 100644 index 0000000..5bf988a Binary files /dev/null and b/app/src/main/res/drawable/round_bg_others_add.png differ diff --git a/app/src/main/res/drawable/round_bg_others_already.xml b/app/src/main/res/drawable/round_bg_others_already.xml new file mode 100644 index 0000000..d440990 --- /dev/null +++ b/app/src/main/res/drawable/round_bg_others_already.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/round_bg_three.xml b/app/src/main/res/drawable/round_bg_three.xml index 082001b..10bd0cf 100644 --- a/app/src/main/res/drawable/round_bg_three.xml +++ b/app/src/main/res/drawable/round_bg_three.xml @@ -2,6 +2,6 @@ android:shape="rectangle"> - + \ No newline at end of file diff --git a/app/src/main/res/drawable/round_bg_two.xml b/app/src/main/res/drawable/round_bg_two.xml index 662018a..0435c16 100644 --- a/app/src/main/res/drawable/round_bg_two.xml +++ b/app/src/main/res/drawable/round_bg_two.xml @@ -2,6 +2,6 @@ android:shape="rectangle"> - + diff --git a/app/src/main/res/drawable/second_add.png b/app/src/main/res/drawable/second_add.png new file mode 100644 index 0000000..922be08 Binary files /dev/null and b/app/src/main/res/drawable/second_add.png differ diff --git a/app/src/main/res/drawable/third_add.png b/app/src/main/res/drawable/third_add.png new file mode 100644 index 0000000..1e474cc Binary files /dev/null and b/app/src/main/res/drawable/third_add.png differ diff --git a/app/src/main/res/layout/activity_onboarding.xml b/app/src/main/res/layout/activity_onboarding.xml index 9be21fc..280434d 100644 --- a/app/src/main/res/layout/activity_onboarding.xml +++ b/app/src/main/res/layout/activity_onboarding.xml @@ -80,9 +80,10 @@ android:src="@drawable/male" /> @@ -117,9 +118,10 @@ android:src="@drawable/female" /> @@ -161,6 +163,7 @@ android:src="@drawable/question_mark_one" /> + + + + + diff --git a/app/src/main/res/layout/ai_keyboard.xml b/app/src/main/res/layout/ai_keyboard.xml index adb2348..a84958e 100644 --- a/app/src/main/res/layout/ai_keyboard.xml +++ b/app/src/main/res/layout/ai_keyboard.xml @@ -13,6 +13,7 @@ android:layout_marginTop="3dp" android:paddingStart="12dp" android:paddingEnd="8dp"> + + app:layout_constraintBottom_toBottomOf="parent" /> - + + - + + + + android:src="@drawable/copy_the_message" /> + - + - - - - - - - - + app:justifyContent="flex_start" + app:alignContent="flex_start"/> - - - + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/keyboard_button_1" + android:layout_marginStart="4dp" + android:orientation="vertical"> - - - + - - + + + + + + + + + + + + - - + + + android:orientation="vertical" /> + - - diff --git a/app/src/main/res/layout/bottom_page_list1.xml b/app/src/main/res/layout/bottom_page_list1.xml index 525dedf..afcdd61 100644 --- a/app/src/main/res/layout/bottom_page_list1.xml +++ b/app/src/main/res/layout/bottom_page_list1.xml @@ -62,18 +62,20 @@ android:text="Loading..." android:textSize="10sp" android:textColor="#1B1F1A" /> - - + android:layout_width="60dp" + android:layout_marginTop="50dp" + android:layout_height="28dp" + android:background="@drawable/round_bg_two"> + + @@ -117,17 +119,19 @@ android:textSize="10sp" android:textColor="#1B1F1A" /> - + android:layout_height="28dp" + android:background="@drawable/round_bg_one"> + + @@ -172,17 +176,20 @@ android:textSize="10sp" android:textColor="#1B1F1A" /> - + android:layout_width="60dp" + android:layout_marginTop="50dp" + android:layout_height="28dp" + android:background="@drawable/round_bg_three"> + + diff --git a/app/src/main/res/layout/dialog_confirm_delete_character.xml b/app/src/main/res/layout/dialog_confirm_delete_character.xml new file mode 100644 index 0000000..e226d4a --- /dev/null +++ b/app/src/main/res/layout/dialog_confirm_delete_character.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/feedback_fragment.xml b/app/src/main/res/layout/feedback_fragment.xml index ece79e8..7685d00 100644 --- a/app/src/main/res/layout/feedback_fragment.xml +++ b/app/src/main/res/layout/feedback_fragment.xml @@ -69,14 +69,17 @@ + android:minLines="4" + android:maxLines="10" + android:scrollbars="vertical" + android:isScrollContainer="true" + android:nestedScrollingEnabled="true" /> + - - - - diff --git a/app/src/main/res/layout/fragment_consumption_record.xml b/app/src/main/res/layout/fragment_consumption_record.xml new file mode 100644 index 0000000..40acb23 --- /dev/null +++ b/app/src/main/res/layout/fragment_consumption_record.xml @@ -0,0 +1,28 @@ + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_mine.xml b/app/src/main/res/layout/fragment_mine.xml index 03938c7..71e6491 100644 --- a/app/src/main/res/layout/fragment_mine.xml +++ b/app/src/main/res/layout/fragment_mine.xml @@ -8,13 +8,13 @@ android:layout_height="match_parent" tools:context=".ui.home.HomeFragment"> - - + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_ai_persona_card.xml b/app/src/main/res/layout/item_ai_persona_card.xml new file mode 100644 index 0000000..2b258e4 --- /dev/null +++ b/app/src/main/res/layout/item_ai_persona_card.xml @@ -0,0 +1,32 @@ + + + + + + + + diff --git a/app/src/main/res/layout/item_keyboard_character.xml b/app/src/main/res/layout/item_keyboard_character.xml new file mode 100644 index 0000000..2d15d1d --- /dev/null +++ b/app/src/main/res/layout/item_keyboard_character.xml @@ -0,0 +1,30 @@ + + + + + + + diff --git a/app/src/main/res/layout/item_loading_footer.xml b/app/src/main/res/layout/item_loading_footer.xml new file mode 100644 index 0000000..882af55 --- /dev/null +++ b/app/src/main/res/layout/item_loading_footer.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/app/src/main/res/layout/item_persona.xml b/app/src/main/res/layout/item_persona.xml index 21943f5..d839a8b 100644 --- a/app/src/main/res/layout/item_persona.xml +++ b/app/src/main/res/layout/item_persona.xml @@ -70,20 +70,23 @@ android:textColor="#02BEAC" android:textSize="10sp" /> - + android:background="@drawable/list_two_bg"> + + - \ No newline at end of file + + + diff --git a/app/src/main/res/layout/item_rank_other.xml b/app/src/main/res/layout/item_rank_other.xml index 555f9c4..0169f3e 100644 --- a/app/src/main/res/layout/item_rank_other.xml +++ b/app/src/main/res/layout/item_rank_other.xml @@ -57,16 +57,17 @@ android:textColor="#9A9A9A" /> - + android:background="@drawable/round_bg_others"> + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_transaction_record.xml b/app/src/main/res/layout/item_transaction_record.xml new file mode 100644 index 0000000..2672418 --- /dev/null +++ b/app/src/main/res/layout/item_transaction_record.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/layout/keyboard.xml b/app/src/main/res/layout/keyboard.xml index 32f7158..2260d70 100644 --- a/app/src/main/res/layout/keyboard.xml +++ b/app/src/main/res/layout/keyboard.xml @@ -1,5 +1,6 @@ - + + android:overScrollMode="never" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + android:id="@+id/completion_HorizontalScrollView"> - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/my_keyboard.xml b/app/src/main/res/layout/my_keyboard.xml index b506bd0..0e682ef 100644 --- a/app/src/main/res/layout/my_keyboard.xml +++ b/app/src/main/res/layout/my_keyboard.xml @@ -56,7 +56,6 @@ android:textSize="16sp" /> - + android:orientation="vertical"> - - - - + - - - - - - - + + \ No newline at end of file diff --git a/app/src/main/res/layout/number_keyboard.xml b/app/src/main/res/layout/number_keyboard.xml index 73c40fb..5f8fd9d 100644 --- a/app/src/main/res/layout/number_keyboard.xml +++ b/app/src/main/res/layout/number_keyboard.xml @@ -68,8 +68,8 @@ android:orientation="horizontal"> + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/sheet_select_gender.xml b/app/src/main/res/layout/sheet_select_gender.xml new file mode 100644 index 0000000..536f0da --- /dev/null +++ b/app/src/main/res/layout/sheet_select_gender.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/symbol_keyboard.xml b/app/src/main/res/layout/symbol_keyboard.xml index 0cf04cf..62596e7 100644 --- a/app/src/main/res/layout/symbol_keyboard.xml +++ b/app/src/main/res/layout/symbol_keyboard.xml @@ -62,16 +62,16 @@ android:gravity="center_horizontal" android:orientation="horizontal"> - - - - - - - - - - + + + + + + + + + + @@ -82,16 +82,16 @@ android:gravity="center_horizontal" android:orientation="horizontal"> - - - - - - - - - - + + + + + + + + + + @@ -101,13 +101,13 @@ android:layout_weight="1" android:gravity="center_horizontal" android:orientation="horizontal"> - - - - - - - + + + + + + + @@ -117,9 +117,9 @@ android:layout_weight="1" android:gravity="center_horizontal" android:orientation="horizontal"> - - - - + + + + diff --git a/app/src/main/res/navigation/global_graph.xml b/app/src/main/res/navigation/global_graph.xml index 25035c3..d2ba096 100644 --- a/app/src/main/res/navigation/global_graph.xml +++ b/app/src/main/res/navigation/global_graph.xml @@ -11,13 +11,6 @@ android:name="com.example.myapplication.ui.EmptyFragment" android:label="empty" /> - - - + + + + + + + + + + + + + + + + + + + 9 Shift Space - Backspace + Backspace ABC/123 , . Enter + 从相册选择 + 拍照 + 更换头像 + 取消 + 头像更新成功 + 上传失败 + 上传出错 diff --git a/app/src/main/res/xml/file_paths.xml b/app/src/main/res/xml/file_paths.xml new file mode 100644 index 0000000..e1eb1c5 --- /dev/null +++ b/app/src/main/res/xml/file_paths.xml @@ -0,0 +1,6 @@ + + + +