diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index ac1e4db8..cdd831f4 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -6,7 +6,6 @@
xmlns:android="http://schemas.android.com/apk/res/android">
-
@@ -14,16 +13,18 @@
android:allowBackup="false"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
- android:theme="@style/AppTheme">
+ android:theme="@style/Theme.AppCompat.DayNight">
-
+
-
+
@@ -33,6 +34,6 @@
android:excludeFromRecents="true"
android:label="@string/add_word_title"
android:name="io.github.sspanak.tt9.ui.AddWordAct"
- android:theme="@android:style/Theme.Dialog"/>
+ android:theme="@style/Theme.AppCompat.DayNight.Dialog"/>
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 00000000..8986f15c
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,76 @@
+# Traditional T9 Contribution Guide
+If you would like to contribute to the project by fixing a bug, adding a new language or something else, read below.
+
+
+## Getting Started
+- If you are about to write code, you need to setup your development environment. See the [Building](#building) section.
+- If you would like to add a new language, follow the [New Language](#adding-a-new-language) section.
+- Finally, see what is the actual [contribution process](#contribution-process) and how to get your code merged.
+
+
+## Building
+The recommended way of building is using Android Studio. As the of time of writing this, the current version is: Android Studio Dolphin | 2021.3.1.
+
+### Building a Debug .apk
+If you have not configured Android Studio yet, follow [the official manual](https://developer.android.com/training/basics/firstapp), then follow the simple steps below to get the project running.
+
+- _Import the project in Android Studio._
+- _Prevent the "Default Activity not found" issue._ The app does not have a default view or a launcher icon. For this reason, you must configure Android Studio not to launch anything after installing, otherwise it will fail with "Default Activity not found" or a similar message. To do so:
+ - Open "Edit Configurations..." (Press Shift 3 times and select it from the command list)
+ - Go to "General" tab.
+ - Change "Launch Options" to "Nothing"
+ - Hit "OK"
+
+That's it! Now you should be able to deploy and debug the app on your device.
+
+You can find more info in this [Github issue](https://github.com/android/input-samples/issues/18).
+
+### Building a Release .apk
+The project is configured to build an unsigned release variant by default.
+
+- Select the "release" variant from Android Studio options (`Build` -> `Select Build Variant...`)
+- `Build` -> `Rebuild Project`. After that, just ignore all warnings until you get to the end of the process.
+- Find the `.apk` in the generated 'build/' folder.
+
+_Note that it may not be possible to install an unsigned `.apk` on newer versions of Android. You must either manually sign it or build a signed one instead._
+
+### Building a Signed .apk
+Make sure you have a signing key. If you don't have one, follow the [official manual](https://developer.android.com/studio/publish/app-signing#sign-apk).
+
+- Select `Build` -> `Generate Signed Bundle / APK...`.
+- Select `APK` and proceed to the next screen.
+- Enter your key details (or create a new one) and continue to the next screen.
+- Choose the "Release" variant, then click `Finish` to start building.
+- Android Studio will tell you where the `.apk` is, but if it does not, try looking for it in the `release/` folder.
+
+## Adding a New Language
+To support a new language one needs to:
+
+- Add status icons
+ - Create a proper icon for each screen size. The icon needs to contain the abbreviation of the language. (e.g. "En" for "English").
+ - The font must be Roboto Lt at an adequate size to fit the icon square with minimum padding.
+ - The text must be white and the background must be transparent as per the [official Android guide](https://android-doc.github.io/guide/practices/ui_guidelines/icon_design_status_bar.html).
+ - To simplify the process, you could use Android Studio. It has a built-in icon generator accessible by right-cicking on "drawable" folder -> New -> Image Asset. Then choose "Icon Type": "Notification Icons", "Asset Type": Text, "Trim": No, "Padding": 0%.
+- Find a suitable dictionary and add it to `assets` folder. Ensure it does not contain single letters. The application will add them automatically.
+- Do not forget to include the dictionary license (or readme) file in the `docs/` folder.
+- Create a new language class in `languages/definitions/`. Make sure to set all properties.
+ - `ID` must be the next available number.
+ - Set `isPunctuationPartOfWords` to `true`, if you need to use the 1-key for typing words, such as: `it's`, `a'tje` or `п'ят`. Otherwise, it would not be possible to type them, nor will they appear as suggestions. `false` will allow faster typing when apostrophes or other punctuation are not part of the words.
+- Add the new language to the list in `LanguageCollection.java`. You only need to add it in one place, in the constructor. Please, be nice and maintain the alphabetical order.
+- Optionally, translate Traditional T9 in your language, by adding `res/values/strings-your-lang`. The Android Studio translation editor is very handy.
+
+
+## Contribution Process
+
+### Before you start
+- Find the issue you are interested in or create a new one.
+- Assign it to yourself, to indicate you are working on it. This will prevent someone else, who is unaware that you are working, to pick it up and do the same thing.
+
+### After you are done
+- Ensure there are no building errors or warnings, and the code works properly.
+- Clean up any commented or unused code.
+- Rebase with the latest `master`. Fix any conflicts, if necessary.
+- Open a pull request and link the issue you are solving.
+- If any review discussions begin, you may need to do some extra improvements.
+- Have in mind, some PRs may be rejected, for example, if you have used third-party code or images without an appropriate license, or if your changes are targeting a very specific phone and breaking functionality on another one. It may be rejected due to other reasons, too.
+- Once all discussions have been resolved, your PR is ready for merging. Congratulations and thank you for the contribution!
\ No newline at end of file
diff --git a/README.md b/README.md
index b7290abf..58c3f5ae 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
# Traditional T9
-TT9 is an IME (Input Method Editor) for Android devices with hardware keypad. It supports multiple languages and predictive text typing. _NOTE: TT9 is not usable on touchscreen-only devices._
+TT9 is an IME (Input Method Editor) for Android devices with a hardware keypad. It supports multiple languages and predictive text typing.
This is an updated version of the [original project](https://github.com/Clam-/TraditionalT9) by Clam-.
@@ -9,73 +9,27 @@ This is an updated version of the [original project](https://github.com/Clam-/Tr
or get the APK from the [Releases Section](https://github.com/sspanak/tt9/releases/latest).
+## System Requirements
+- Android 4.4 or higher. _(Tested and confirmed on Android 4.4.2, 5.1.1 and 11)_
+- Free space:
+ - Minimum 10 Mb when not using Predictive mode and no dictionaries are loaded.
+ - About 25 Mb per each enabled language in Predictive mode
+- A hardware keypad or a keyboard. The application is not usable on touchscreen-only devices.
+
+_If you own a phone with Android 2.2 up to 4.4, please refer to the original version of Traditional T9 from 2016._
+
## Using Traditional T9
-If you just wish to install and use TT9, see the [user manual](docs/user-manual.md). You don't need to read anything below this line.
+If you just wish to install and use Traditional T9, check out the [user manual](docs/user-manual.md) for useful tips and a list of keypad shortcuts.
-## Building
-The recommended way of building is using Android Studio. As the of time of writing this, the current version is: Android Studio Dolphin | 2021.3.1.
-
-### Building a Debug .apk
-If you have not configured Android Studio yet, follow [the official manual](https://developer.android.com/training/basics/firstapp), then follow the simple steps below to get the project running.
-
-- _Import the project in Android Studio._
-- _Prevent the "Default Activity not found" issue._ The app does not have a default view or a launcher icon. For this reason, you must configure Android Studio not to launch anything after installing, otherwise it will fail with "Default Activity not found" or a similar message. To do so:
- - Open "Edit Configurations..." (Press Shift 3 times and select it from the command list)
- - Go to "General" tab.
- - Change "Launch Options" to "Nothing"
- - Hit "OK"
-
-That's it! Now you should be able to deploy and debug the app on your device.
-
-You can find more info in this [Github issue](https://github.com/android/input-samples/issues/18).
-
-### Building a Release .apk
-The project is configured to build an unsigned release variant by default.
-
-- Select the "release" variant from Android Studio options (`Build` -> `Select Build Variant...`)
-- `Build` -> `Rebuild Project`. After that, just ignore all warnings until you get to the end of the process.
-- Find the `.apk` in the generated 'build/' folder.
-
-_Note that it may not be possible to install an unsigned `.apk` on newer versions of Android. You must either manually sign it or build a signed one instead._
-
-### Building a Signed .apk
-Make sure you have a signing key. If you don't have one, follow the [official manual](https://developer.android.com/studio/publish/app-signing#sign-apk).
-
-- Select `Build` -> `Generate Signed Bundle / APK...`.
-- Select `APK` and proceed to the next screen.
-- Enter your key details (or create a new one) and continue to the next screen.
-- Choose the "Release" variant, then click `Finish` to start building.
-- Android Studio will tell you where the `.apk` is, but if it does not, try looking for it in the `release/` folder.
-
-## Adding a new language
-To support a new language one needs to:
-
-- Add status icons
- - Create a proper icon for each screen size. The icon needs to contain the abbreviation of the language. (e.g. "En" for "English").
- - The font must be Roboto Lt at an adequate size to fit the icon square with minimum padding.
- - The text must be white and the background must be transparent as per the [official Android guide](https://android-doc.github.io/guide/practices/ui_guidelines/icon_design_status_bar.html).
- - To simplify the process, you could use Android Studio. It has a built-in icon generator accessible by right-cicking on "drawable" folder -> New -> Image Asset. Then choose "Icon Type": "Notification Icons", "Asset Type": Text, "Trim": No, "Padding": 0%.
-- Find a suitable dictionary and add it to `assets` folder.
-- Create a new language class in `languages/definitions/`. Make sure to set all properties.
- - `ID` must be the next available number. Currently, the range is limited between 1 and 31, so there can be 31 languages in total.
- - Set `isPunctuationPartOfWords` to `true`, if you need to use the 1-key for typing words, such as: `it's`, `a'tje` or `п'ят`. Otherwise, it would not be possible to type them, nor will they appear as suggestions. `false` is recommended when apostrophes or other punctuation are not part of the words, to allow faster typing.
-- Add the new language to the list in `LanguageCollection.java`. You only need to add it in one place, in the constructor. Please, be nice and maintain the alphabetical order.
-- Add a new entry in `res/values/const.xml`. Make sure the new ID matches the one in the language class.
-- Add new entries in `res/values/arrays.xml`.
-- Add translations in `res/values/strings-your-lang`. The Android Studio translation editor is very handy.
-
-## Word Lists
-Here is detailed information and licenses about the word lists used:
-- [Bulgarian word list](docs/bgWordlistReadme.txt)
-- [English word list](docs/enWordlistReadme.txt)
-- [French word list](docs/frWordlistReadme.txt)
-- [German word list](docs/deWordlistReadme.txt)
-- [Russian word list](docs/ruWordlistReadme.txt)
-- [Ukrainian word list](docs/ukWordlistReadme.txt)
+## Contributing to the Project
+As with many other open-source projects, this one is also maintained by its author in his free time. Any help in making Traditional T9 better will be highly appreciated. This includes:
+- Testing on different phones and reporting bugs or other unusual behavior. Currently, the only testing and development device is: Qin F21 Pro+ / Android 11.
+- Adding new translations or fixing incorrect ones.
+- Writing new code. If you are a developer and you are willing fix a bug, add a new language or maybe create a brand new feature, see the [Contribution Guide](CONTRIBUTING.md).
## License
- The source code, the logo image and the icons are licensed under the conditions described in [LICENSE.txt](LICENSE.txt).
-- The word lists / dictionaries are licensed under the licenses provided in the [respective readme files](#word-lists), where applicable.
+- The dictionaries are licensed under the licenses provided in the [respective readme files](docs/dictionaries/), where applicable. Detailed information about the dictionaries is also available there.
- [Silver foil photo created by rawpixel.com - www.freepik.com](https://www.freepik.com/photos/silver-foil)
- "Roboto" font is under [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0).
- "Negotiate" and "Vibrocentric" fonts are under [The Fontspring Desktop/Ebook Font End User License](docs/desktop-ebook-EULA-1.8.txt).
diff --git a/docs/bgWordlistReadme.txt b/docs/dictionaries/bgWordlistReadme.txt
similarity index 100%
rename from docs/bgWordlistReadme.txt
rename to docs/dictionaries/bgWordlistReadme.txt
diff --git a/docs/deWordlistReadme.txt b/docs/dictionaries/deWordlistReadme.txt
similarity index 100%
rename from docs/deWordlistReadme.txt
rename to docs/dictionaries/deWordlistReadme.txt
diff --git a/docs/enWordlistReadme.txt b/docs/dictionaries/enWordlistReadme.txt
similarity index 100%
rename from docs/enWordlistReadme.txt
rename to docs/dictionaries/enWordlistReadme.txt
diff --git a/docs/frWordlistReadme.txt b/docs/dictionaries/frWordlistReadme.txt
similarity index 100%
rename from docs/frWordlistReadme.txt
rename to docs/dictionaries/frWordlistReadme.txt
diff --git a/docs/nlWordlistReadme.txt b/docs/dictionaries/nlWordlistReadme.txt
similarity index 100%
rename from docs/nlWordlistReadme.txt
rename to docs/dictionaries/nlWordlistReadme.txt
diff --git a/docs/ruWordlistReadme.txt b/docs/dictionaries/ruWordlistReadme.txt
similarity index 100%
rename from docs/ruWordlistReadme.txt
rename to docs/dictionaries/ruWordlistReadme.txt
diff --git a/docs/ukWordlistReadme.txt b/docs/dictionaries/ukWordlistReadme.txt
similarity index 100%
rename from docs/ukWordlistReadme.txt
rename to docs/dictionaries/ukWordlistReadme.txt
diff --git a/docs/user-manual.md b/docs/user-manual.md
index 0e2c5725..45c7bc69 100644
--- a/docs/user-manual.md
+++ b/docs/user-manual.md
@@ -4,7 +4,7 @@ TT9 is an IME (Input Method Editor) for Android devices with hardware keypad. It
All Source code and documentation are available on Github: [https://github.com/sspanak/tt9](https://github.com/sspanak/tt9).
## Initial Setup
-In order to use Traditional T9, you need to enable it as an Android keyboard. To do so:
+After installing, in order to use Traditional T9, you need to enable it as an Android keyboard. To do so:
- Go to Android Settings → System → Languages → Keyboards.
- Add Traditional T9 IME.
@@ -56,26 +56,32 @@ _Predictive mode only._
- **In 123 mode:** type the respective number.
- **In ABC and Predictive mode:** type a letter/punctuation character or hold to type the respective number.
-#### Text Mode Key (Hash/Pound/#):
-- **Short press:** Cycle input modes (abc → ABC → Predictive → 123)
-- **Short press while typing:** Change between UPPERCASE/lowercase.
-- **Long press:** Select the next language.
-- **Number-only fields:** Type a "#". Changing the mode is not possible in such fields.
+#### Add Word Key (Default: Press ✱):
+Add a new word to the dictionary for the current language.
-#### Other Actions Key (Star/✱):
-- **Short press:** Add a new word to the dictionary.
-- **Long press:** Open the Configration screen.
+#### Backspace Key (Default: Press ↩ / Back):
+Just deletes text.
-#### Backspace Key (Back/↩):
-- Just deletes text.
-
-**Note:** "Back" key plays a somewhat special role in Android. This role needs to be preserved for your phone to remain usable. Have in mind the notes below:
+**Note:** The default "Back" key plays a somewhat special role in Android. This role needs to be preserved for your phone to remain usable. Have in mind the notes below:
- **Short Press when there is no text**: Go back to the previous screen (the system default action).
- **Short Press when there is text:** Some applications, most notably Firefox and Spotify, take full control of the "Back" key. This means, it may function as the application authors intended, instead of as backspace. In such cases, you could use the on-screen backspace instead. Unfortunately, nothing else could be done, because this is a restriction posed by Android.
- **Long Press**: Whatever the system default action is (i.e. show running applications list).
-## On-screen soft keys
-All functionality is available using the keypad, but for convenience, on touchscreen phones or the ones with customizable function keys, you could also use the on-screen soft keys.
+All this does not apply, when using other keys. They will just delete text
+
+#### Next Input Mode Key (Default: Press #):
+- **Press when there are no suggestions:** Cycle the input modes (abc → ABC → Predictive → 123). Note that only 123 mode is available in numeric fields and Predictive mode is not available in password fields.
+- **Press while suggestions are on:** Toggle the suggestions between UPPERCASE and lowercase.
+- **Number-only fields:** No special action. Type a "#" with the default key. Changing the mode is not possible in such fields.
+
+#### Next Language Key (Default: Hold #):
+Select the next language, when mulitple languages have been enabled from the Settings.
+
+#### Settings Key (Default: Hold ✱):
+Open the Configration screen.
+
+## On-screen Soft keys
+All functionality is available using the keypad, but for convenience, on touchscreen phones, you could also use the on-screen keys. If you instead prefer to have more screen space, disable them from the Settings.
#### Left Soft Key:
Open the [Settings screen](#settings-screen).
@@ -84,18 +90,18 @@ Open the [Settings screen](#settings-screen).
Backspace.
## Settings Screen
-On the Configuration screen, you can choose your preferred languages, load a dictionary for Predictive mode or view this manual.
+On the Settings screen, you can choose languages for typing, configure the keypad hotkeys or change the application appearance.
To access it:
- Start typing in a text field to wake up TT9.
-- Use the on-screen gear button or hold Other Actions Key.
+- Use the on-screen gear button or hold the Settings Key.
## License
- The source code, the logo image and the icons are licensed under [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0).
-- The word lists / dictionaries are licensed under the licenses provided in the respective readme files found in the source code, where applicable.
+- The dictionaries are licensed under the licenses provided in the [respective readme files](dictionaries/), where applicable. Detailed information about the dictionaries is also available there.
- [Silver foil photo created by rawpixel.com - www.freepik.com](https://www.freepik.com/photos/silver-foil)
- "Roboto" font is under [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0).
-- "Negotiate" and "Vibrocentric" fonts are under [The Fontspring Desktop/Ebook Font End User License](desktop-ebook-EULA-1.8.txt)
+- "Negotiate" and "Vibrocentric" fonts are under [The Fontspring Desktop/Ebook Font End User License](desktop-ebook-EULA-1.8.txt).
## Privacy Policy
Traditional T9 does not collect any information about you or about the way you are using using the application.
diff --git a/res/drawable-hdpi/btn_circle_disable.png b/res/drawable-hdpi/btn_circle_disable.png
deleted file mode 100644
index 917bc284..00000000
Binary files a/res/drawable-hdpi/btn_circle_disable.png and /dev/null differ
diff --git a/res/drawable-hdpi/btn_circle_disable_focused.png b/res/drawable-hdpi/btn_circle_disable_focused.png
deleted file mode 100644
index 749ebb12..00000000
Binary files a/res/drawable-hdpi/btn_circle_disable_focused.png and /dev/null differ
diff --git a/res/drawable-hdpi/btn_circle_normal.png b/res/drawable-hdpi/btn_circle_normal.png
deleted file mode 100644
index 72a4388c..00000000
Binary files a/res/drawable-hdpi/btn_circle_normal.png and /dev/null differ
diff --git a/res/drawable-hdpi/btn_circle_pressed.png b/res/drawable-hdpi/btn_circle_pressed.png
deleted file mode 100644
index 90651cee..00000000
Binary files a/res/drawable-hdpi/btn_circle_pressed.png and /dev/null differ
diff --git a/res/drawable-hdpi/btn_circle_selected.png b/res/drawable-hdpi/btn_circle_selected.png
deleted file mode 100644
index e5b58e1e..00000000
Binary files a/res/drawable-hdpi/btn_circle_selected.png and /dev/null differ
diff --git a/res/drawable-hdpi/ic_btn_round_more_disabled.png b/res/drawable-hdpi/ic_btn_round_more_disabled.png
deleted file mode 100644
index 31291273..00000000
Binary files a/res/drawable-hdpi/ic_btn_round_more_disabled.png and /dev/null differ
diff --git a/res/drawable-hdpi/ic_btn_round_more_normal.png b/res/drawable-hdpi/ic_btn_round_more_normal.png
deleted file mode 100644
index 3b0f76b6..00000000
Binary files a/res/drawable-hdpi/ic_btn_round_more_normal.png and /dev/null differ
diff --git a/res/drawable/btn_circle.xml b/res/drawable/btn_circle.xml
deleted file mode 100644
index 9df5abbc..00000000
--- a/res/drawable/btn_circle.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/res/drawable/bggradient.xml b/res/drawable/button_background.xml
similarity index 65%
rename from res/drawable/bggradient.xml
rename to res/drawable/button_background.xml
index 52ca3c9f..84f2e578 100644
--- a/res/drawable/bggradient.xml
+++ b/res/drawable/button_background.xml
@@ -1,9 +1,8 @@
-
diff --git a/res/drawable/button_grad.xml b/res/drawable/button_background_dark.xml
similarity index 100%
rename from res/drawable/button_grad.xml
rename to res/drawable/button_background_dark.xml
diff --git a/res/drawable/button_custom.xml b/res/drawable/button_custom.xml
deleted file mode 100644
index 2f3497c4..00000000
--- a/res/drawable/button_custom.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
diff --git a/res/drawable/button_grad_press.xml b/res/drawable/button_separator.xml
similarity index 65%
rename from res/drawable/button_grad_press.xml
rename to res/drawable/button_separator.xml
index be81f300..42b30796 100644
--- a/res/drawable/button_grad_press.xml
+++ b/res/drawable/button_separator.xml
@@ -1,9 +1,8 @@
-
diff --git a/res/drawable/button_separator_dark.xml b/res/drawable/button_separator_dark.xml
new file mode 100644
index 00000000..5be65e01
--- /dev/null
+++ b/res/drawable/button_separator_dark.xml
@@ -0,0 +1,8 @@
+
+
+
+
diff --git a/res/drawable/ic_btn_round_more.xml b/res/drawable/ic_btn_round_more.xml
deleted file mode 100644
index b4bfc879..00000000
--- a/res/drawable/ic_btn_round_more.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
diff --git a/res/drawable/suggestion_separator.xml b/res/drawable/suggestion_separator.xml
index ba68ceec..2b394bb1 100644
--- a/res/drawable/suggestion_separator.xml
+++ b/res/drawable/suggestion_separator.xml
@@ -1,5 +1,5 @@
-
+
diff --git a/res/layout/checkbox.xml b/res/layout/checkbox.xml
deleted file mode 100644
index 349d6397..00000000
--- a/res/layout/checkbox.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
-
-
-
-
diff --git a/res/layout/mainview.xml b/res/layout/mainview.xml
index 3ee4a495..ced08e29 100644
--- a/res/layout/mainview.xml
+++ b/res/layout/mainview.xml
@@ -8,63 +8,61 @@
android:id="@+id/main_suggestions_list"
android:layout_width="fill_parent"
android:layout_height="@dimen/candidate_list_height"
+ android:fadingEdge="horizontal"
android:orientation="horizontal"
- android:scrollbars="none"
- android:background="@color/candidate_background" />
+ android:paddingTop="1px"
+ android:scrollbars="none" />
+ android:textSize="24sp"/>
+
+ android:text="@android:string/ok" />
+
diff --git a/res/layout/preference_dialog.xml b/res/layout/preference_dialog.xml
deleted file mode 100644
index 0d88ebf8..00000000
--- a/res/layout/preference_dialog.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/res/layout/preference_list_content.xml b/res/layout/preference_list_content.xml
deleted file mode 100644
index 56792935..00000000
--- a/res/layout/preference_list_content.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
-
diff --git a/res/layout/preferences_container.xml b/res/layout/preferences_container.xml
new file mode 100644
index 00000000..c15650e3
--- /dev/null
+++ b/res/layout/preferences_container.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/res/layout/setting.xml b/res/layout/setting.xml
deleted file mode 100644
index 7b07e2f5..00000000
--- a/res/layout/setting.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/res/layout/setting_sum.xml b/res/layout/setting_sum.xml
deleted file mode 100644
index c93ce998..00000000
--- a/res/layout/setting_sum.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/res/layout/setting_widget.xml b/res/layout/setting_widget.xml
deleted file mode 100644
index e7ced890..00000000
--- a/res/layout/setting_widget.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/res/layout/suggestion_list_view.xml b/res/layout/suggestion_list_view.xml
index 08d37408..e86f2744 100644
--- a/res/layout/suggestion_list_view.xml
+++ b/res/layout/suggestion_list_view.xml
@@ -12,6 +12,5 @@
android:paddingHorizontal="@dimen/candidate_padding_horizontal"
android:paddingVertical="@dimen/candidate_padding_vertical"
android:text=""
- android:textColor="@color/candidate_color"
android:textSize="@dimen/candidate_font_size" />
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index 30e497c7..34b8a0d2 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -1,5 +1,5 @@
-
+Настройки на Traditional T9ЗатвориЗавършено
@@ -12,21 +12,34 @@
За да добавите нова дума, първо изберете език.Добавяне на дума
+ За приложениетоПомощ
+ Тъмен обликИзбор на езици
- Зареди речник
- Зареди свой речник
- Изтрий речник
+ Изтрий речник
+ Речници
+ Отмени зарежданетоНеуспешно зареждане. Невалидна дума \"%1$s\" на ред %2$d за език \"%3$s\".
- Зареждането на речник е отменено.Несупешно зареждане на речник за език \"%1$s\" (%2$s).Неуспешно зареждане на речник.Зареждането на речник приключи.Зареждане на речник (%1$s)…
- Зареждане на вашия речник…
- Зареждане на речник
+ Зареди речникНеуспешно зареждане. Липсва речник за \"%1$s\".Речникът е изтрит успешно.
+ Облик
+ Бутони за бърз достъп
+ Бутони на екрана
+ Назад
+ Зелена слушалка
+ Добавяне на нова дума
+ Триене на текст
+ Следващ eзик
+ Режим на писане
+ Настройки
+ Възстанови стандартните бутони
+ Възстановени са стандартните \"бързи\" бутони.
+ (задръж)
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 1a48fec7..cace4c40 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -10,14 +10,14 @@
Das Wort \"%1$s\" ist bereits in Wörterbuch.Wort hinzufügen
- "Hilfe anzeigen
- Sprachen
- Wörterbuch laden
- Benutzerwörterbuch laden
- Wörterbuch löschen
+ Über die Anwendung
+ Hilfe
+ Dunkles Thema
+ Sprachen auswählen
+ Wörterbuch löschen
+ WörterbücherLade Wörterbuch (%1$s)…
- Lade Benutzerwörterbuch…Wörterbuch ladenWird nicht geladen. Wörterbuch für \"%1$s\" nicht gefunden.
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index dce4b7b8..ee8b8396 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -11,18 +11,20 @@
Le mot «%1$s» est déjà dans le dictionnaire.Ajouter un mot
- "Afficher l\'aide
- Choisir langues
- Charger le dictionnaire
- Charger le dictionnaire utilisateur
- Supprimer le dictionaire
+ À propos de l\'application
+ Aide
+ Thème sombre
+ Choisir langues
+ Supprimer le dictionaire
+
+ Dictionnaires
+ Annuler le chargementEchec du chargement de dictionnaire pour langue «%1$s» (%2$s).
- Chargement du dictionnaire annulée.Echec du chargement de dictionnaire.Chargement du dictionnaire terminé.Chargement du dictionnaire (%1$s)…
- Chargement du dictionnaire utilisateur…Charger le dictionnaireEchec du chargement. Dictionnaire «%1$s» introuvable.
+ Raccourcis clavier
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index c89472b4..8acdff28 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -11,18 +11,19 @@
Parola “%1$s” già nel dizionario.Aggiungi parola
- "Mostra aiuto
- Le lingue
- Carica dizionario
- Carica dizionario utente
- Eliminare il dizionario
+ Sull\'applicazione
+ Aiuto
+ Tema scuro
+ Scegli le lingue
+ Eliminare il dizionario
- Caricamento del dizionario annullato.
+ Dizionari
+ Annullare il caricamentoCaricamento del dizionario non riuscito.Caricamento del dizionario terminato.Caricamento del dizionario (%1$s)…
- Caricamento dizionario utente…
- Caricamento del dizionario
+ Carica il dizionarioImpossibile caricare. Dizionario per “%1$s” non trovato.
+ Scorciatoie da tastiera
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index f9105524..9db95499 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -1,5 +1,5 @@
-
+Traditional T9 OptiesSluitenEr is een onverwachte fout opgetreden.
@@ -10,13 +10,13 @@
Woord \"%1$s\" staat al in het woordenboek.Kan geen woord invoegen als er geen taal is geselecteerd.Woord toevoegen
- Laat help zien
+ Over de applicatie
+ Helpen
+ Donker themaTalen kiezen
- Woordenboek laden
- Gebruikerswoordenboek laden
- Woordenboek wissen
+ Woordenboek wissen
+ WoordenboekenWoordenboek laden (%1$s)…
- Gebruikerswoordenboek laden…Woordenboek ladenLaden mislukt. Woordenboek voor %1$s niet gevonden.Woordenboek succesvol gewist.
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index f8e018ef..373817cc 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -12,18 +12,18 @@
Чтобы добавить новое слово, сначала выберите язык.Добавить слово
- О программе
+ О приложении
+ Помощь
+ Темная темаВыбор языков
- Загрузить словарь
- Загрузить свой словарь
- Очистить словарь
+ Очистить словарь
- Загрузка словаря отменена.
+ Словари
+ Отменить загрузкуОшибка загрузки словаря для языка «%1$s» (%2$s).Ошибка загрузки словаря.Загрузка словаря завершена.Загрузка словаря (%1$s)…
- Загрузка пользовательского словаря…Загрузить словарьОшибка загрузки. Словарь «%1$s» не найден.Словарь успешно очищен.
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 01bfc6c2..8f2a0338 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -11,18 +11,18 @@
Слово «%1$s» вже є в словнику.Додати слово
- Про програму
+ Про додаток
+ Допомога
+ Темна темаВибір мови
- Завантажити словник
- Завантажити свій словник
- Очистити словник
+ Очистити словник
- Завантаження словника скасовано.
+ Словники
+ Скасувати завантаженняПомилка завантаження словника для мови «%1$s» (%2$s).Помилка завантаження словника.Завантаження словника завершено.Завантаження словника (%1$s)…
- Завантаження словника користувача…Завантажити словникПомилка завантаження. Словник «%1$s» не знайдено.
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
deleted file mode 100644
index 392253f5..00000000
--- a/res/values/arrays.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
- English
- Русский
- Deutsch
- Français
- Italiano
- Українська
- Български
- Nederlands
-
-
-
- @integer/LANG_EN
- @integer/LANG_RU
- @integer/LANG_DE
- @integer/LANG_FR
- @integer/LANG_IT
- @integer/LANG_UK
- @integer/LANG_BG
- @integer/LANG_NL
-
-
diff --git a/res/values/colors.xml b/res/values/colors.xml
index b0e0dea4..6314cd6f 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -1,8 +1,17 @@
- #EFEBE9
- #333333
- #CCCCCC
- #555555
+
+ #242424
+
+ #CECECE
+ #202020
+ #AAAAAA#888888
+
+
+ #C0C0C0
+
+ #333333
+ #CCCCCC
+ #555555
diff --git a/res/values/const.xml b/res/values/const.xml
deleted file mode 100644
index f7d791ff..00000000
--- a/res/values/const.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
-
- @integer/LANG_EN
-
diff --git a/res/values/strings.xml b/res/values/strings.xml
index d450abe8..9d4a6721 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1,5 +1,5 @@
-
+https://github.com/sspanak/tt9/blob/master/docs/user-manual.mdTraditional T9Traditional T9 Settings
@@ -14,21 +14,45 @@
Cannot insert a word when no language is selected.Add Word
- Show Help
- Choose Languages
- Load dictionary
- Load user dictionary
- SDcard/traditionalt9/user.lang.dict (lang: en/ru/de/fr)
- Clear dictionary
+ About
+ Appearance
+ Dictionaries
+ Select Hotkeys
+ Choose Languages
+ Dark Theme
+ Show on-screen keys
+ Help
+
+ Cancel loadingLoading failed. Invalid word \"%1$s\" on line %2$d of language \"%3$s\".
- Dictionary load cancelled.Failed loading the dictionary for language \"%1$s\" (%2$s).Dictionary load failed.Dictionary load completed.Loading dictionary (%1$s)…
- Loading user dictionary…Load dictionaryLoading failed. Dictionary for \"%1$s\" not found.
+ Clear dictionaryDictionary successfully cleared.
+
+ Add Word key
+ Backspace key
+ Next Language key
+ Input Mode key
+ Show Settings key
+ Restore Default Keys
+ Default key settings restored.
+
+ (hold)
+ --
+ Back
+ Call
+ Delete
+ F1
+ F2
+ F3
+ F4
+ Menu
+ #
+ ✱
diff --git a/res/values/styles.xml b/res/values/styles.xml
index fae1512c..8ceac149 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -1,24 +1,9 @@
+
-
-
-
-
-
-
-
-
-
-
-
diff --git a/res/xml/method.xml b/res/xml/method.xml
index 3872386b..7d0fc133 100644
--- a/res/xml/method.xml
+++ b/res/xml/method.xml
@@ -22,5 +22,5 @@
+ android:settingsActivity="io.github.sspanak.tt9.preferences.PreferencesActivity">
diff --git a/res/xml/prefs.xml b/res/xml/prefs.xml
index d8d7be9b..101f3737 100644
--- a/res/xml/prefs.xml
+++ b/res/xml/prefs.xml
@@ -1,14 +1,109 @@
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/io/github/sspanak/tt9/db/DictionaryLoader.java b/src/io/github/sspanak/tt9/db/DictionaryLoader.java
index 311ba18d..41faf1cf 100644
--- a/src/io/github/sspanak/tt9/db/DictionaryLoader.java
+++ b/src/io/github/sspanak/tt9/db/DictionaryLoader.java
@@ -17,11 +17,13 @@ import io.github.sspanak.tt9.Logger;
import io.github.sspanak.tt9.languages.InvalidLanguageCharactersException;
import io.github.sspanak.tt9.languages.InvalidLanguageException;
import io.github.sspanak.tt9.languages.Language;
-import io.github.sspanak.tt9.preferences.T9Preferences;
+import io.github.sspanak.tt9.preferences.SettingsStore;
public class DictionaryLoader {
+ private static DictionaryLoader self;
+
private final AssetManager assets;
- private final T9Preferences prefs;
+ private final SettingsStore settings;
private final Pattern containsPunctuation = Pattern.compile("\\p{Punct}(? 0) {
int progress = (int) Math.floor(100.0 * line / totalWords);
- sendProgressMessage(handler, language, progress, prefs.getDictionaryImportProgressUpdateInterval());
+ sendProgressMessage(handler, language, progress, settings.getDictionaryImportProgressUpdateInterval());
}
}
diff --git a/src/io/github/sspanak/tt9/ime/InputFieldHelper.java b/src/io/github/sspanak/tt9/ime/InputFieldHelper.java
index 55d5881c..03d7c345 100644
--- a/src/io/github/sspanak/tt9/ime/InputFieldHelper.java
+++ b/src/io/github/sspanak/tt9/ime/InputFieldHelper.java
@@ -71,7 +71,7 @@ class InputFieldHelper {
* determineInputModes
* Determine the typing mode based on the input field being edited. Returns an ArrayList of the allowed modes.
*
- * @return ArrayList
+ * @return ArrayList
*/
public static ArrayList determineInputModes(EditorInfo inputField) {
final int INPUT_TYPE_SHARP_007H_PHONE_BOOK = 65633;
@@ -134,7 +134,7 @@ class InputFieldHelper {
*/
public static void determineTextCase(EditorInfo inputField) {
// Logger.d("updateShift", "CM start: " + mCapsMode);
- // if (inputField != null && mCapsMode != T9Preferences.CASE_UPPER) {
+ // if (inputField != null && mCapsMode != SettingsStore.CASE_UPPER) {
// int caps = 0;
// if (inputField.inputType != InputType.TYPE_NULL) {
// caps = currentInputConnection.getCursorCapsMode(inputField.inputType);
@@ -142,13 +142,13 @@ class InputFieldHelper {
// // mInputView.setShifted(mCapsLock || caps != 0);
// // Logger.d("updateShift", "caps: " + caps);
// if ((caps & TextUtils.CAP_MODE_CHARACTERS) == TextUtils.CAP_MODE_CHARACTERS) {
- // mCapsMode = T9Preferences.CASE_UPPER;
+ // mCapsMode = SettingsStore.CASE_UPPER;
// } else if ((caps & TextUtils.CAP_MODE_SENTENCES) == TextUtils.CAP_MODE_SENTENCES) {
- // mCapsMode = T9Preferences.CASE_CAPITALIZE;
+ // mCapsMode = SettingsStore.CASE_CAPITALIZE;
// } else if ((caps & TextUtils.CAP_MODE_WORDS) == TextUtils.CAP_MODE_WORDS) {
- // mCapsMode = T9Preferences.CASE_CAPITALIZE;
+ // mCapsMode = SettingsStore.CASE_CAPITALIZE;
// } else {
- // mCapsMode = T9Preferences.CASE_LOWER;
+ // mCapsMode = SettingsStore.CASE_LOWER;
// }
// updateStatusIcon();
// }
diff --git a/src/io/github/sspanak/tt9/ime/InputModeValidator.java b/src/io/github/sspanak/tt9/ime/InputModeValidator.java
index c12d502e..4c650750 100644
--- a/src/io/github/sspanak/tt9/ime/InputModeValidator.java
+++ b/src/io/github/sspanak/tt9/ime/InputModeValidator.java
@@ -7,10 +7,10 @@ import io.github.sspanak.tt9.ime.modes.InputMode;
import io.github.sspanak.tt9.languages.Language;
import io.github.sspanak.tt9.languages.LanguageCollection;
import io.github.sspanak.tt9.languages.definitions.English;
-import io.github.sspanak.tt9.preferences.T9Preferences;
+import io.github.sspanak.tt9.preferences.SettingsStore;
public class InputModeValidator {
- public static ArrayList validateEnabledLanguages(T9Preferences prefs, ArrayList enabledLanguageIds) {
+ public static ArrayList validateEnabledLanguages(SettingsStore settings, ArrayList enabledLanguageIds) {
ArrayList validLanguages = LanguageCollection.getAll(enabledLanguageIds);
ArrayList validLanguageIds = new ArrayList<>();
for (Language lang : validLanguages) {
@@ -21,12 +21,12 @@ public class InputModeValidator {
Logger.e("tt9/validateEnabledLanguages", "The language list seems to be corrupted. Resetting to first language only.");
}
- prefs.saveEnabledLanguages(validLanguageIds);
+ settings.saveEnabledLanguageIds(validLanguageIds);
return validLanguageIds;
}
- public static Language validateLanguage(T9Preferences prefs, Language language, ArrayList validLanguageIds) {
+ public static Language validateLanguage(SettingsStore settings, Language language, ArrayList validLanguageIds) {
if (language != null && validLanguageIds.contains(language.getId())) {
return language;
}
@@ -36,20 +36,20 @@ public class InputModeValidator {
Language validLanguage = LanguageCollection.getLanguage(validLanguageIds.get(0));
validLanguage = validLanguage == null ? LanguageCollection.getLanguage(1) : validLanguage;
validLanguage = validLanguage == null ? new English() : validLanguage;
- prefs.saveInputLanguage(validLanguage.getId());
+ settings.saveInputLanguage(validLanguage.getId());
Logger.w("tt9/validateSavedLanguage", error + " Enforcing language: " + validLanguage.getId());
return validLanguage;
}
- public static InputMode validateMode(T9Preferences prefs, InputMode inputMode, ArrayList allowedModes) {
+ public static InputMode validateMode(SettingsStore settings, InputMode inputMode, ArrayList allowedModes) {
if (allowedModes.size() > 0 && allowedModes.contains(inputMode.getId())) {
return inputMode;
}
InputMode newMode = InputMode.getInstance(allowedModes.size() > 0 ? allowedModes.get(0) : InputMode.MODE_123);
- prefs.saveInputMode(newMode);
+ settings.saveInputMode(newMode);
if (newMode.getId() != inputMode.getId()) {
Logger.w("tt9/validateMode", "Invalid input mode: " + inputMode.getId() + " Enforcing: " + newMode.getId());
@@ -58,12 +58,12 @@ public class InputModeValidator {
return newMode;
}
- public static void validateTextCase(T9Preferences prefs, InputMode inputMode, int newTextCase) {
+ public static void validateTextCase(SettingsStore settings, InputMode inputMode, int newTextCase) {
if (!inputMode.setTextCase(newTextCase)) {
inputMode.defaultTextCase();
Logger.w("tt9/validateTextCase", "Invalid text case: " + newTextCase + " Enforcing: " + inputMode.getTextCase());
}
- prefs.saveTextCase(inputMode.getTextCase());
+ settings.saveTextCase(inputMode.getTextCase());
}
}
diff --git a/src/io/github/sspanak/tt9/ime/KeyPadHandler.java b/src/io/github/sspanak/tt9/ime/KeyPadHandler.java
index a8c2f6a1..95626d0f 100644
--- a/src/io/github/sspanak/tt9/ime/KeyPadHandler.java
+++ b/src/io/github/sspanak/tt9/ime/KeyPadHandler.java
@@ -7,13 +7,13 @@ import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
-import io.github.sspanak.tt9.preferences.T9Preferences;
+import io.github.sspanak.tt9.preferences.SettingsStore;
abstract class KeyPadHandler extends InputMethodService {
protected InputConnection currentInputConnection = null;
- protected T9Preferences prefs;
+ protected SettingsStore settings;
// editing mode
protected static final int NON_EDIT = 0;
@@ -44,7 +44,7 @@ abstract class KeyPadHandler extends InputMethodService {
@Override
public void onCreate() {
super.onCreate();
- prefs = new T9Preferences(getApplicationContext());
+ settings = new SettingsStore(getApplicationContext());
onInit();
}
@@ -130,7 +130,7 @@ abstract class KeyPadHandler extends InputMethodService {
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (isOff()) {
- return super.onKeyDown(keyCode, event);
+ return false;
}
// Logger.d("onKeyDown", "Key: " + event + " repeat?: " + event.getRepeatCount() + " long-time: " + event.isLongPress());
@@ -138,7 +138,7 @@ abstract class KeyPadHandler extends InputMethodService {
// "backspace" key must repeat its function, when held down, so we handle it in a special way
// Also dialer fields seem to handle backspace on their own and we must ignore it,
// otherwise, keyDown race condition occur for all keys.
- if (mEditing != EDITING_DIALER && keyCode == prefs.getKeyBackspace()) {
+ if (mEditing != EDITING_DIALER && keyCode == settings.getKeyBackspace()) {
boolean isThereTextBefore = InputFieldHelper.isThereText(currentInputConnection);
boolean backspaceHandleStatus = handleBackspaceHold();
@@ -170,8 +170,7 @@ abstract class KeyPadHandler extends InputMethodService {
}
if (
- keyCode == prefs.getKeyOtherActions()
- || keyCode == prefs.getKeyInputMode()
+ isSpecialFunctionKey(keyCode)
|| keyCode == KeyEvent.KEYCODE_STAR
|| keyCode == KeyEvent.KEYCODE_POUND
|| (isNumber(keyCode) && shouldTrackNumPress())
@@ -189,7 +188,7 @@ abstract class KeyPadHandler extends InputMethodService {
@Override
public boolean onKeyLongPress(int keyCode, KeyEvent event) {
if (isOff()) {
- return super.onKeyDown(keyCode, event);
+ return false;
}
// Logger.d("onLongPress", "LONG PRESS: " + keyCode);
@@ -200,12 +199,8 @@ abstract class KeyPadHandler extends InputMethodService {
ignoreNextKeyUp = keyCode;
- if (keyCode == prefs.getKeyOtherActions()) {
- return onKeyOtherAction(true);
- }
-
- if (keyCode == prefs.getKeyInputMode()) {
- return onKeyInputMode(true);
+ if (handleSpecialFunctionKey(keyCode, true)) {
+ return true;
}
switch (keyCode) {
@@ -256,7 +251,7 @@ abstract class KeyPadHandler extends InputMethodService {
if (
mEditing != EDITING_DIALER // dialer fields seem to handle backspace on their own
- && keyCode == prefs.getKeyBackspace()
+ && keyCode == settings.getKeyBackspace()
&& InputFieldHelper.isThereText(currentInputConnection)
) {
return true;
@@ -277,12 +272,8 @@ abstract class KeyPadHandler extends InputMethodService {
return false;
}
- if (keyCode == prefs.getKeyOtherActions()) {
- return onKeyOtherAction(false);
- }
-
- if (keyCode == prefs.getKeyInputMode()) {
- return onKeyInputMode(false);
+ if (handleSpecialFunctionKey(keyCode, false)) {
+ return true;
}
switch(keyCode) {
@@ -321,6 +312,27 @@ abstract class KeyPadHandler extends InputMethodService {
}
+ private boolean handleSpecialFunctionKey(int keyCode, boolean hold) {
+ if (keyCode == settings.getKeyAddWord() * (hold ? -1 : 1)) {
+ return onKeyAddWord();
+ }
+
+ if (keyCode == settings.getKeyNextLanguage() * (hold ? -1 : 1)) {
+ return onKeyNextLanguage();
+ }
+
+ if (keyCode == settings.getKeyNextInputMode() * (hold ? -1 : 1)) {
+ return onKeyNextInputMode();
+ }
+
+ if (keyCode == settings.getKeyShowSettings() * (hold ? -1 : 1)) {
+ return onKeyShowSettings();
+ }
+
+ return false;
+ }
+
+
private boolean isOff() {
return currentInputConnection == null || mEditing == NON_EDIT;
}
@@ -331,6 +343,15 @@ abstract class KeyPadHandler extends InputMethodService {
}
+ private boolean isSpecialFunctionKey(int keyCode) {
+ return keyCode == settings.getKeyAddWord()
+ || keyCode == settings.getKeyBackspace()
+ || keyCode == settings.getKeyNextLanguage()
+ || keyCode == settings.getKeyNextInputMode()
+ || keyCode == settings.getKeyShowSettings();
+ }
+
+
protected void resetKeyRepeat() {
isNumKeyRepeated = false;
lastNumKeyCode = 0;
@@ -381,8 +402,10 @@ abstract class KeyPadHandler extends InputMethodService {
abstract protected boolean onPound();
// customized key handlers
- abstract protected boolean onKeyInputMode(boolean hold);
- abstract protected boolean onKeyOtherAction(boolean hold);
+ abstract protected boolean onKeyAddWord();
+ abstract protected boolean onKeyNextLanguage();
+ abstract protected boolean onKeyNextInputMode();
+ abstract protected boolean onKeyShowSettings();
// helpers
abstract protected void onInit();
diff --git a/src/io/github/sspanak/tt9/ime/SoftKeyHandler.java b/src/io/github/sspanak/tt9/ime/SoftKeyHandler.java
index f6ce2162..51aff3f2 100644
--- a/src/io/github/sspanak/tt9/ime/SoftKeyHandler.java
+++ b/src/io/github/sspanak/tt9/ime/SoftKeyHandler.java
@@ -1,8 +1,13 @@
package io.github.sspanak.tt9.ime;
+import android.graphics.drawable.Drawable;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
+import android.widget.Button;
+import android.widget.LinearLayout;
+
+import androidx.core.content.ContextCompat;
import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.ui.UI;
@@ -12,16 +17,16 @@ class SoftKeyHandler implements View.OnTouchListener {
private final TraditionalT9 tt9;
private View view = null;
- public SoftKeyHandler(LayoutInflater layoutInflater, TraditionalT9 tt9) {
+ public SoftKeyHandler(TraditionalT9 tt9) {
this.tt9 = tt9;
- createView(layoutInflater);
+ getView();
}
- View createView(LayoutInflater layoutInflater) {
+ View getView() {
if (view == null) {
- view = layoutInflater.inflate(R.layout.mainview, null);
+ view = LayoutInflater.from(tt9.getApplicationContext()).inflate(R.layout.mainview, null);
for (int buttonId : buttons) {
view.findViewById(buttonId).setOnTouchListener(this);
@@ -31,10 +36,6 @@ class SoftKeyHandler implements View.OnTouchListener {
return view;
}
- View getView() {
- return view;
- }
-
void show() {
if (view != null) {
@@ -50,13 +51,63 @@ class SoftKeyHandler implements View.OnTouchListener {
}
+ void setSoftKeysVisibility(boolean visible) {
+ if (view != null) {
+ view.findViewById(R.id.main_soft_keys).setVisibility(visible ? LinearLayout.VISIBLE : LinearLayout.GONE);
+ }
+ }
+
+
+ /** setDarkTheme
+ * Changes the main view colors according to the theme.
+ *
+ * We need to do this manually, instead of relying on the Context to resolve the appropriate colors,
+ * because this View is part of the main service View. And service Views are always locked to the
+ * system context and theme.
+ *
+ * More info:
+ * https://stackoverflow.com/questions/72382886/system-applies-night-mode-to-views-added-in-service-type-application-overlay
+ */
+ void setDarkTheme(boolean darkEnabled) {
+ if (view == null) {
+ return;
+ }
+
+ // background
+ view.findViewById(R.id.main_soft_keys).setBackground(ContextCompat.getDrawable(
+ view.getContext(),
+ darkEnabled ? R.drawable.button_background_dark : R.drawable.button_background
+ ));
+
+ // text
+ int textColor = ContextCompat.getColor(
+ view.getContext(),
+ darkEnabled ? R.color.dark_button_text : R.color.button_text
+ );
+
+ for (int buttonId : buttons) {
+ Button button = view.findViewById(buttonId);
+ button.setTextColor(textColor);
+ }
+
+ // separators
+ Drawable separatorColor = ContextCompat.getDrawable(
+ view.getContext(),
+ darkEnabled ? R.drawable.button_separator_dark : R.drawable.button_separator
+ );
+
+ view.findViewById(R.id.main_separator_left).setBackground(separatorColor);
+ view.findViewById(R.id.main_separator_right).setBackground(separatorColor);
+ }
+
+
@Override
public boolean onTouch(View view, MotionEvent event) {
int action = event.getAction();
int buttonId = view.getId();
if (buttonId == R.id.main_left && action == MotionEvent.ACTION_UP) {
- UI.showPreferencesScreen(tt9);
+ UI.showSettingsScreen(tt9);
return view.performClick();
}
diff --git a/src/io/github/sspanak/tt9/ime/Util.java b/src/io/github/sspanak/tt9/ime/TextHelper.java
similarity index 97%
rename from src/io/github/sspanak/tt9/ime/Util.java
rename to src/io/github/sspanak/tt9/ime/TextHelper.java
index d9b6a297..66b65bfa 100644
--- a/src/io/github/sspanak/tt9/ime/Util.java
+++ b/src/io/github/sspanak/tt9/ime/TextHelper.java
@@ -8,7 +8,7 @@ import android.text.style.UnderlineSpan;
import io.github.sspanak.tt9.Logger;
-public class Util {
+public class TextHelper {
public static CharSequence highlightComposingText(CharSequence word, int start, int end) {
if (end < start || start < 0) {
Logger.w("tt9.util.highlightComposingText", "Cannot highlight invalid composing text range: [" + start + ", " + end + "]");
diff --git a/src/io/github/sspanak/tt9/ime/TraditionalT9.java b/src/io/github/sspanak/tt9/ime/TraditionalT9.java
index 9bf4097d..d8b0e81d 100644
--- a/src/io/github/sspanak/tt9/ime/TraditionalT9.java
+++ b/src/io/github/sspanak/tt9/ime/TraditionalT9.java
@@ -40,17 +40,24 @@ public class TraditionalT9 extends KeyPadHandler {
}
- private void loadPreferences() {
- mLanguage = LanguageCollection.getLanguage(prefs.getInputLanguage());
- mEnabledLanguages = prefs.getEnabledLanguages();
- mInputMode = InputMode.getInstance(prefs.getInputMode());
- mInputMode.setTextCase(prefs.getTextCase());
+ private void loadSettings() {
+ mLanguage = LanguageCollection.getLanguage(settings.getInputLanguage());
+ mEnabledLanguages = settings.getEnabledLanguageIds();
+ mInputMode = InputMode.getInstance(settings.getInputMode());
+ mInputMode.setTextCase(settings.getTextCase());
}
private void validateLanguages() {
- mEnabledLanguages = InputModeValidator.validateEnabledLanguages(prefs, mEnabledLanguages);
- mLanguage = InputModeValidator.validateLanguage(prefs, mLanguage, mEnabledLanguages);
+ mEnabledLanguages = InputModeValidator.validateEnabledLanguages(settings, mEnabledLanguages);
+ mLanguage = InputModeValidator.validateLanguage(settings, mLanguage, mEnabledLanguages);
+ }
+
+
+ private void validateFunctionKeys() {
+ if (!settings.areFunctionKeysSet()) {
+ settings.setDefaultKeys();
+ }
}
@@ -58,35 +65,42 @@ public class TraditionalT9 extends KeyPadHandler {
self = this;
if (softKeyHandler == null) {
- softKeyHandler = new SoftKeyHandler(getLayoutInflater(), this);
+ softKeyHandler = new SoftKeyHandler(this);
}
if (mSuggestionView == null) {
mSuggestionView = new SuggestionsView(softKeyHandler.getView());
}
- loadPreferences();
- prefs.clearLastWord();
+ loadSettings();
+ validateFunctionKeys();
+ settings.clearLastWord();
}
protected void onRestart(EditorInfo inputField) {
- // in case we are back from Preferences screen, update the language list
- mEnabledLanguages = prefs.getEnabledLanguages();
+ // in case we are back from Settings screen, update the language list
+ mEnabledLanguages = settings.getEnabledLanguageIds();
validateLanguages();
// some input fields support only numbers or do not accept predictions
determineAllowedInputModes(inputField);
- mInputMode = InputModeValidator.validateMode(prefs, mInputMode, allowedInputModes);
+ mInputMode = InputModeValidator.validateMode(settings, mInputMode, allowedInputModes);
// Some modes may want to change the default text case based on grammar rules.
determineNextTextCase();
- InputModeValidator.validateTextCase(prefs, mInputMode, prefs.getTextCase());
+ InputModeValidator.validateTextCase(settings, mInputMode, settings.getTextCase());
// build the UI
- clearSuggestions();
UI.updateStatusIcon(this, mLanguage, mInputMode);
+
+ clearSuggestions();
+ mSuggestionView.setDarkTheme(settings.getDarkTheme());
+
+ softKeyHandler.setDarkTheme(settings.getDarkTheme());
+ softKeyHandler.setSoftKeysVisibility(settings.getShowSoftKeys());
softKeyHandler.show();
+
if (!isInputViewShown()) {
showWindow(true);
}
@@ -239,32 +253,42 @@ public class TraditionalT9 extends KeyPadHandler {
}
- protected boolean onKeyInputMode(boolean hold) {
- if (mEditing == EDITING_DIALER) {
- return false;
- }
-
- if (hold) {
- nextLang();
- } else {
- nextInputMode();
- }
-
- return true;
- }
-
-
- protected boolean onKeyOtherAction(boolean hold) {
+ protected boolean onKeyAddWord() {
if (mEditing == EDITING_NOSHOW || mEditing == EDITING_DIALER) {
return false;
}
- if (hold) {
- UI.showPreferencesScreen(this);
- } else {
- showAddWord();
+ showAddWord();
+ return true;
+ }
+
+
+ protected boolean onKeyNextLanguage() {
+ if (mEditing == EDITING_DIALER) {
+ return false;
}
+ nextLang();
+ return true;
+ }
+
+
+ protected boolean onKeyNextInputMode() {
+ if (mEditing == EDITING_DIALER) {
+ return false;
+ }
+
+ nextInputMode();
+ return true;
+ }
+
+
+ protected boolean onKeyShowSettings() {
+ if (mEditing == EDITING_NOSHOW || mEditing == EDITING_DIALER) {
+ return false;
+ }
+
+ UI.showSettingsScreen(this);
return true;
}
@@ -407,7 +431,7 @@ public class TraditionalT9 extends KeyPadHandler {
private void setComposingTextWithWordStemIndication(CharSequence word) {
if (mInputMode.getWordStem().length() > 0) {
- setComposingText(Util.highlightComposingText(word, 0, mInputMode.getWordStem().length()));
+ setComposingText(TextHelper.highlightComposingText(word, 0, mInputMode.getWordStem().length()));
} else {
setComposingText(word);
}
@@ -441,8 +465,8 @@ public class TraditionalT9 extends KeyPadHandler {
}
// save the settings for the next time
- prefs.saveInputMode(mInputMode);
- prefs.saveTextCase(mInputMode.getTextCase());
+ settings.saveInputMode(mInputMode);
+ settings.saveTextCase(mInputMode.getTextCase());
UI.updateStatusIcon(this, mLanguage, mInputMode);
}
@@ -463,7 +487,7 @@ public class TraditionalT9 extends KeyPadHandler {
validateLanguages();
// save it for the next time
- prefs.saveInputLanguage(mLanguage.getId());
+ settings.saveInputLanguage(mLanguage.getId());
UI.updateStatusIcon(this, mLanguage, mInputMode);
}
@@ -483,7 +507,7 @@ public class TraditionalT9 extends KeyPadHandler {
private void determineAllowedInputModes(EditorInfo inputField) {
allowedInputModes = InputFieldHelper.determineInputModes(inputField);
- int lastInputModeId = prefs.getInputMode();
+ int lastInputModeId = settings.getInputMode();
if (allowedInputModes.contains(lastInputModeId)) {
mInputMode = InputMode.getInstance(lastInputModeId);
} else if (allowedInputModes.contains(InputMode.MODE_ABC)) {
@@ -527,8 +551,8 @@ public class TraditionalT9 extends KeyPadHandler {
* If a new word was added to the dictionary, this function will append add it to the current input field.
*/
private void restoreAddedWordIfAny() {
- String word = prefs.getLastWord();
- prefs.clearLastWord();
+ String word = settings.getLastWord();
+ settings.clearLastWord();
if (word.length() == 0 || word.equals(InputFieldHelper.getSurroundingWord(currentInputConnection))) {
return;
@@ -549,6 +573,6 @@ public class TraditionalT9 extends KeyPadHandler {
* Generates the actual UI of TT9.
*/
protected View createSoftKeyView() {
- return softKeyHandler.createView(getLayoutInflater());
+ return softKeyHandler.getView();
}
}
diff --git a/src/io/github/sspanak/tt9/ime/modes/ModePredictive.java b/src/io/github/sspanak/tt9/ime/modes/ModePredictive.java
index b07f51c5..ac008a1d 100644
--- a/src/io/github/sspanak/tt9/ime/modes/ModePredictive.java
+++ b/src/io/github/sspanak/tt9/ime/modes/ModePredictive.java
@@ -11,7 +11,7 @@ import io.github.sspanak.tt9.Logger;
import io.github.sspanak.tt9.db.DictionaryDb;
import io.github.sspanak.tt9.languages.Language;
import io.github.sspanak.tt9.languages.Punctuation;
-import io.github.sspanak.tt9.preferences.T9Preferences;
+import io.github.sspanak.tt9.preferences.SettingsStore;
public class ModePredictive extends InputMode {
public int getId() { return MODE_PREDICTIVE; }
@@ -194,8 +194,8 @@ public class ModePredictive extends InputMode {
language,
digitSequence,
stem,
- T9Preferences.getInstance().getSuggestionsMin(),
- T9Preferences.getInstance().getSuggestionsMax()
+ SettingsStore.getInstance().getSuggestionsMin(),
+ SettingsStore.getInstance().getSuggestionsMax()
);
return true;
diff --git a/src/io/github/sspanak/tt9/languages/Language.java b/src/io/github/sspanak/tt9/languages/Language.java
index a9182fa2..e68f70fa 100644
--- a/src/io/github/sspanak/tt9/languages/Language.java
+++ b/src/io/github/sspanak/tt9/languages/Language.java
@@ -1,5 +1,7 @@
package io.github.sspanak.tt9.languages;
+import androidx.annotation.NonNull;
+
import java.util.ArrayList;
import java.util.Locale;
@@ -102,4 +104,10 @@ public class Language {
return sequence.toString();
}
+
+ @NonNull
+ @Override
+ public String toString() {
+ return name != null ? name : "";
+ }
}
diff --git a/src/io/github/sspanak/tt9/languages/LanguageCollection.java b/src/io/github/sspanak/tt9/languages/LanguageCollection.java
index 8584988b..32e95f54 100644
--- a/src/io/github/sspanak/tt9/languages/LanguageCollection.java
+++ b/src/io/github/sspanak/tt9/languages/LanguageCollection.java
@@ -1,7 +1,10 @@
package io.github.sspanak.tt9.languages;
+import android.os.Build;
+
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
@@ -57,7 +60,7 @@ public class LanguageCollection {
return null;
}
- public static ArrayList getAll(ArrayList languageIds) {
+ public static ArrayList getAll(ArrayList languageIds, boolean sort) {
ArrayList langList = new ArrayList<>();
for (int languageId : languageIds) {
@@ -67,6 +70,41 @@ public class LanguageCollection {
}
}
+ if (sort && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ langList.sort(Comparator.comparing(l -> l.getLocale().toString()));
+ }
+
return langList;
}
+
+ public static ArrayList getAll(ArrayList languageIds) {
+ return getAll(languageIds, false);
+ }
+
+ public static ArrayList getAll(boolean sort) {
+ ArrayList langList = new ArrayList<>(getInstance().languages.values());
+
+ if (sort && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ langList.sort(Comparator.comparing(l -> l.getLocale().toString()));
+ }
+
+ return langList;
+ }
+
+ public static ArrayList getAll() {
+ return getAll(false);
+ }
+
+
+ public static String toString(ArrayList list) {
+ StringBuilder stringList = new StringBuilder();
+ int listSize = list.size();
+
+ for (int i = 0; i < listSize; i++) {
+ stringList.append(list.get(i));
+ stringList.append((i < listSize - 1) ? ", " : " ");
+ }
+
+ return stringList.toString();
+ }
}
diff --git a/src/io/github/sspanak/tt9/languages/definitions/Bulgarian.java b/src/io/github/sspanak/tt9/languages/definitions/Bulgarian.java
index 8224bb66..405e5e8a 100644
--- a/src/io/github/sspanak/tt9/languages/definitions/Bulgarian.java
+++ b/src/io/github/sspanak/tt9/languages/definitions/Bulgarian.java
@@ -11,7 +11,7 @@ import io.github.sspanak.tt9.languages.Punctuation;
public class Bulgarian extends Language {
public Bulgarian() {
id = 7;
- name = "български";
+ name = "Български";
locale = new Locale("bg","BG");
dictionaryFile = "bg-utf8.txt";
icon = R.drawable.ime_lang_bg;
diff --git a/src/io/github/sspanak/tt9/languages/definitions/Russian.java b/src/io/github/sspanak/tt9/languages/definitions/Russian.java
index c039720c..8c77f889 100644
--- a/src/io/github/sspanak/tt9/languages/definitions/Russian.java
+++ b/src/io/github/sspanak/tt9/languages/definitions/Russian.java
@@ -11,7 +11,7 @@ import io.github.sspanak.tt9.languages.Punctuation;
public class Russian extends Language {
public Russian() {
id = 2;
- name = "русский";
+ name = "Русский";
locale = new Locale("ru","RU");
dictionaryFile = "ru-utf8.txt";
icon = R.drawable.ime_lang_ru;
diff --git a/src/io/github/sspanak/tt9/languages/definitions/Ukrainian.java b/src/io/github/sspanak/tt9/languages/definitions/Ukrainian.java
index 0ce93175..e9190730 100644
--- a/src/io/github/sspanak/tt9/languages/definitions/Ukrainian.java
+++ b/src/io/github/sspanak/tt9/languages/definitions/Ukrainian.java
@@ -11,7 +11,7 @@ import io.github.sspanak.tt9.languages.Punctuation;
public class Ukrainian extends Language {
public Ukrainian() {
id = 6;
- name = "українська";
+ name = "Українська";
locale = new Locale("uk","UA");
dictionaryFile = "uk-utf8.txt";
icon = R.drawable.ime_lang_uk;
diff --git a/src/io/github/sspanak/tt9/preferences/ItemClickable.java b/src/io/github/sspanak/tt9/preferences/ItemClickable.java
new file mode 100644
index 00000000..403c6123
--- /dev/null
+++ b/src/io/github/sspanak/tt9/preferences/ItemClickable.java
@@ -0,0 +1,17 @@
+package io.github.sspanak.tt9.preferences;
+
+import androidx.preference.Preference;
+
+public abstract class ItemClickable {
+ protected final Preference item;
+
+ ItemClickable(Preference item) {
+ this.item = item;
+ }
+
+ public void enableClickHandler() {
+ item.setOnPreferenceClickListener(this::onClick);
+ }
+
+ abstract protected boolean onClick(Preference p);
+}
diff --git a/src/io/github/sspanak/tt9/preferences/ItemLoadDictionary.java b/src/io/github/sspanak/tt9/preferences/ItemLoadDictionary.java
new file mode 100644
index 00000000..ecf37ed3
--- /dev/null
+++ b/src/io/github/sspanak/tt9/preferences/ItemLoadDictionary.java
@@ -0,0 +1,83 @@
+package io.github.sspanak.tt9.preferences;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+import androidx.preference.Preference;
+
+import java.util.ArrayList;
+
+import io.github.sspanak.tt9.R;
+import io.github.sspanak.tt9.db.DictionaryImportAlreadyRunningException;
+import io.github.sspanak.tt9.db.DictionaryLoader;
+import io.github.sspanak.tt9.languages.Language;
+import io.github.sspanak.tt9.languages.LanguageCollection;
+import io.github.sspanak.tt9.ui.DictionaryLoadingBar;
+import io.github.sspanak.tt9.ui.UI;
+
+
+public class ItemLoadDictionary extends ItemClickable {
+ public static final String NAME = "dictionary_load";
+
+ private final Context context;
+ private final DictionaryLoader loader;
+ private final DictionaryLoadingBar progressBar;
+
+
+ ItemLoadDictionary(Preference item, Context context, DictionaryLoader loader, DictionaryLoadingBar progressBar) {
+ super(item);
+
+ this.context = context;
+ this.loader = loader;
+ this.progressBar = progressBar;
+
+ if (!progressBar.isCompleted() && !progressBar.isFailed()) {
+ changeToCancelButton();
+ }
+ }
+
+
+ private final Handler onDictionaryLoading = new Handler(Looper.getMainLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ progressBar.show(msg.getData());
+
+ if (progressBar.isCompleted()) {
+ changeToLoadButton();
+ UI.toast(context, R.string.dictionary_loaded);
+ } else if (progressBar.isFailed()) {
+ changeToLoadButton();
+ UI.toast(context, R.string.dictionary_load_failed);
+ }
+ }
+ };
+
+
+ @Override
+ protected boolean onClick(Preference p) {
+ ArrayList languages = LanguageCollection.getAll(SettingsStore.getInstance().getEnabledLanguageIds());
+ progressBar.setFileCount(languages.size());
+
+ try {
+ loader.load(onDictionaryLoading, languages);
+ changeToCancelButton();
+ } catch (DictionaryImportAlreadyRunningException e) {
+ loader.stop();
+ changeToLoadButton();
+ }
+
+ return false;
+ }
+
+
+ public void changeToCancelButton() {
+ item.setTitle(context.getString(R.string.dictionary_cancel_load));
+ }
+
+
+ public void changeToLoadButton() {
+ item.setTitle(context.getString(R.string.dictionary_load_title));
+ }
+}
diff --git a/src/io/github/sspanak/tt9/preferences/ItemResetKeys.java b/src/io/github/sspanak/tt9/preferences/ItemResetKeys.java
new file mode 100644
index 00000000..3c74f97d
--- /dev/null
+++ b/src/io/github/sspanak/tt9/preferences/ItemResetKeys.java
@@ -0,0 +1,33 @@
+package io.github.sspanak.tt9.preferences;
+
+import android.content.Context;
+
+import androidx.preference.Preference;
+
+import io.github.sspanak.tt9.R;
+import io.github.sspanak.tt9.ui.UI;
+
+
+public class ItemResetKeys extends ItemClickable {
+ public static final String NAME = "reset_keys";
+
+ private final Context context;
+ private final SectionKeymap dropdowns;
+ private final SettingsStore settings;
+
+
+ ItemResetKeys(Preference item, Context context, SectionKeymap dropdowns, SettingsStore settings) {
+ super(item);
+ this.context = context;
+ this.dropdowns = dropdowns;
+ this.settings = settings;
+ }
+
+ @Override
+ protected boolean onClick(Preference p) {
+ settings.setDefaultKeys();
+ dropdowns.reloadSettings();
+ UI.toast(context, R.string.function_reset_keys_done);
+ return false;
+ }
+}
diff --git a/src/io/github/sspanak/tt9/preferences/ItemSelectLanguage.java b/src/io/github/sspanak/tt9/preferences/ItemSelectLanguage.java
new file mode 100644
index 00000000..c63b5349
--- /dev/null
+++ b/src/io/github/sspanak/tt9/preferences/ItemSelectLanguage.java
@@ -0,0 +1,76 @@
+package io.github.sspanak.tt9.preferences;
+
+import androidx.preference.MultiSelectListPreference;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+
+import io.github.sspanak.tt9.languages.Language;
+import io.github.sspanak.tt9.languages.LanguageCollection;
+
+public class ItemSelectLanguage {
+ public static final String NAME = "pref_languages";
+
+ private final SettingsStore settings;
+ private final MultiSelectListPreference item;
+
+ ItemSelectLanguage(MultiSelectListPreference multiSelect, SettingsStore settings) {
+ this.item = multiSelect;
+ this.settings = settings;
+ }
+
+ public ItemSelectLanguage populate() {
+ if (item == null) {
+ return this;
+ }
+
+ ArrayList languages = LanguageCollection.getAll(true);
+
+ ArrayList values = new ArrayList<>();
+ for (Language l : languages) {
+ values.add(String.valueOf(l.getId()));
+ }
+
+ ArrayList keys = new ArrayList<>();
+ for (Language l : languages) {
+ keys.add(l.getName());
+ }
+
+ item.setEntries(keys.toArray(new CharSequence[0]));
+ item.setEntryValues(values.toArray(new CharSequence[0]));
+ item.setValues(settings.getEnabledLanguagesIdsAsStrings());
+ previewSelection();
+
+ return this;
+ }
+
+
+ public ItemSelectLanguage enableValidation() {
+ if (item == null) {
+ return this;
+ }
+
+ item.setOnPreferenceChangeListener((preference, newValue) -> {
+ HashSet newLanguages = (HashSet) newValue;
+ if (newLanguages.size() == 0) {
+ newLanguages.add("1");
+ }
+
+ settings.saveEnabledLanguageIds(newLanguages);
+ item.setValues(settings.getEnabledLanguagesIdsAsStrings());
+ previewSelection();
+
+ // we validate and save manually above, so "false" disables automatic save
+ return false;
+ });
+
+ return this;
+ }
+
+
+ private void previewSelection() {
+ item.setSummary(
+ LanguageCollection.toString(LanguageCollection.getAll(settings.getEnabledLanguageIds(), true))
+ );
+ }
+}
diff --git a/src/io/github/sspanak/tt9/preferences/ItemToggleDarkTheme.java b/src/io/github/sspanak/tt9/preferences/ItemToggleDarkTheme.java
new file mode 100644
index 00000000..68293198
--- /dev/null
+++ b/src/io/github/sspanak/tt9/preferences/ItemToggleDarkTheme.java
@@ -0,0 +1,27 @@
+package io.github.sspanak.tt9.preferences;
+
+import androidx.appcompat.app.AppCompatDelegate;
+import androidx.preference.Preference;
+import androidx.preference.SwitchPreferenceCompat;
+
+public class ItemToggleDarkTheme {
+ public static final String NAME = "pref_dark_theme";
+
+ private final SwitchPreferenceCompat themeToggle;
+
+ public ItemToggleDarkTheme(SwitchPreferenceCompat item) {
+ themeToggle = item;
+ }
+
+ public void enableToggleHandler() {
+ themeToggle.setOnPreferenceChangeListener(this::onChange);
+ }
+
+ private boolean onChange(Preference p, Object newValue) {
+ AppCompatDelegate.setDefaultNightMode(
+ ((boolean) newValue) ? AppCompatDelegate.MODE_NIGHT_YES : AppCompatDelegate.MODE_NIGHT_NO
+ );
+
+ return true;
+ }
+}
diff --git a/src/io/github/sspanak/tt9/preferences/ItemTruncateDictionary.java b/src/io/github/sspanak/tt9/preferences/ItemTruncateDictionary.java
new file mode 100644
index 00000000..9c34bff8
--- /dev/null
+++ b/src/io/github/sspanak/tt9/preferences/ItemTruncateDictionary.java
@@ -0,0 +1,49 @@
+package io.github.sspanak.tt9.preferences;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+import androidx.preference.Preference;
+
+import io.github.sspanak.tt9.R;
+import io.github.sspanak.tt9.db.DictionaryDb;
+import io.github.sspanak.tt9.db.DictionaryLoader;
+import io.github.sspanak.tt9.ui.UI;
+
+
+public class ItemTruncateDictionary extends ItemClickable {
+ public static final String NAME = "dictionary_truncate";
+
+ private final Context context;
+ private final DictionaryLoader loader;
+ private final ItemLoadDictionary loadItem;
+
+
+ ItemTruncateDictionary(Preference item, ItemLoadDictionary loadItem, Context context, DictionaryLoader loader) {
+ super(item);
+ this.context = context;
+ this.loadItem = loadItem;
+ this.loader = loader;
+ }
+
+ private final Handler onDictionaryTruncated = new Handler(Looper.getMainLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ UI.toast(context, R.string.dictionary_truncated);
+ }
+ };
+
+ @Override
+ protected boolean onClick(Preference p) {
+ if (loader != null && loader.isRunning()) {
+ loader.stop();
+ loadItem.changeToLoadButton();
+ }
+
+ DictionaryDb.truncateWords(onDictionaryTruncated);
+
+ return false;
+ }
+}
diff --git a/src/io/github/sspanak/tt9/preferences/PreferencesActivity.java b/src/io/github/sspanak/tt9/preferences/PreferencesActivity.java
new file mode 100644
index 00000000..97d308cd
--- /dev/null
+++ b/src/io/github/sspanak/tt9/preferences/PreferencesActivity.java
@@ -0,0 +1,50 @@
+package io.github.sspanak.tt9.preferences;
+
+import android.os.Bundle;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.app.AppCompatDelegate;
+
+import io.github.sspanak.tt9.R;
+import io.github.sspanak.tt9.db.DictionaryLoader;
+import io.github.sspanak.tt9.ui.DictionaryLoadingBar;
+
+public class PreferencesActivity extends AppCompatActivity {
+ SettingsStore settings;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ settings = new SettingsStore(this);
+
+ applyTheme();
+ buildScreen();
+ }
+
+
+ private void applyTheme() {
+ AppCompatDelegate.setDefaultNightMode(
+ settings.getDarkTheme() ? AppCompatDelegate.MODE_NIGHT_YES : AppCompatDelegate.MODE_NIGHT_NO
+ );
+ }
+
+
+ private void buildScreen() {
+ setContentView(R.layout.preferences_container);
+ getSupportFragmentManager()
+ .beginTransaction()
+ .replace(R.id.preferences_container, new PreferencesFragment(this))
+ .commit();
+ }
+
+
+ DictionaryLoadingBar getDictionaryProgressBar() {
+ return DictionaryLoadingBar.getInstance(this);
+ }
+
+
+ DictionaryLoader getDictionaryLoader() {
+ return DictionaryLoader.getInstance(this);
+ }
+}
diff --git a/src/io/github/sspanak/tt9/preferences/PreferencesFragment.java b/src/io/github/sspanak/tt9/preferences/PreferencesFragment.java
new file mode 100644
index 00000000..da28b36b
--- /dev/null
+++ b/src/io/github/sspanak/tt9/preferences/PreferencesFragment.java
@@ -0,0 +1,107 @@
+package io.github.sspanak.tt9.preferences;
+
+import android.os.Bundle;
+
+import androidx.preference.DropDownPreference;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceFragmentCompat;
+
+import java.util.Arrays;
+
+import io.github.sspanak.tt9.BuildConfig;
+import io.github.sspanak.tt9.Logger;
+import io.github.sspanak.tt9.R;
+
+public class PreferencesFragment extends PreferenceFragmentCompat {
+ private PreferencesActivity activity;
+
+ public PreferencesFragment() {
+ super();
+ init();
+ }
+
+ public PreferencesFragment(PreferencesActivity activity) {
+ super();
+ this.activity = activity;
+ init();
+ }
+
+
+ private void init() {
+ if (activity == null) {
+ activity = (PreferencesActivity) getActivity();
+ }
+ }
+
+
+ @Override
+ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+ setPreferencesFromResource(R.xml.prefs, rootKey);
+
+ if (activity == null) {
+ Logger.w(
+ "tt9/PreferencesFragment",
+ "Starting up without an Activity. Preference Items will not be fully initialized."
+ );
+ return;
+ }
+
+ createDictionarySection();
+ createAppearanceSection();
+ createKeymapSection();
+ createAboutSection();
+ }
+
+
+ private void createDictionarySection() {
+ ItemSelectLanguage multiSelect = new ItemSelectLanguage(
+ findPreference(ItemSelectLanguage.NAME),
+ activity.settings
+ );
+ multiSelect.populate().enableValidation();
+
+ ItemLoadDictionary loadItem = new ItemLoadDictionary(
+ findPreference(ItemLoadDictionary.NAME),
+ activity,
+ activity.getDictionaryLoader(),
+ activity.getDictionaryProgressBar()
+ );
+ loadItem.enableClickHandler();
+
+ ItemTruncateDictionary truncateItem = new ItemTruncateDictionary(
+ findPreference(ItemTruncateDictionary.NAME),
+ loadItem,
+ activity,
+ activity.getDictionaryLoader()
+ );
+ truncateItem.enableClickHandler();
+ }
+
+
+ private void createAppearanceSection() {
+ (new ItemToggleDarkTheme(findPreference(ItemToggleDarkTheme.NAME))).enableToggleHandler();
+ }
+
+
+ private void createKeymapSection() {
+ DropDownPreference[] dropDowns = {
+ findPreference(SectionKeymap.ITEM_ADD_WORD),
+ findPreference(SectionKeymap.ITEM_BACKSPACE),
+ findPreference(SectionKeymap.ITEM_NEXT_INPUT_MODE),
+ findPreference(SectionKeymap.ITEM_NEXT_LANGUAGE),
+ findPreference(SectionKeymap.ITEM_SHOW_SETTINGS),
+ };
+ SectionKeymap section = new SectionKeymap(Arrays.asList(dropDowns), activity, activity.settings);
+ section.populate().activate();
+
+ (new ItemResetKeys(findPreference(ItemResetKeys.NAME), activity, section, activity.settings))
+ .enableClickHandler();
+ }
+
+ private void createAboutSection() {
+ Preference vi = findPreference("version_info");
+ if (vi != null) {
+ vi.setSummary(BuildConfig.VERSION_NAME);
+ }
+ }
+}
diff --git a/src/io/github/sspanak/tt9/preferences/SectionKeymap.java b/src/io/github/sspanak/tt9/preferences/SectionKeymap.java
new file mode 100644
index 00000000..c49909f6
--- /dev/null
+++ b/src/io/github/sspanak/tt9/preferences/SectionKeymap.java
@@ -0,0 +1,219 @@
+package io.github.sspanak.tt9.preferences;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.ViewConfiguration;
+
+import androidx.preference.DropDownPreference;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.Objects;
+
+import io.github.sspanak.tt9.Logger;
+import io.github.sspanak.tt9.R;
+
+public class SectionKeymap {
+ public static final String ITEM_ADD_WORD = "key_add_word";
+ public static final String ITEM_BACKSPACE = "key_backspace";
+ public static final String ITEM_NEXT_INPUT_MODE = "key_next_input_mode";
+ public static final String ITEM_NEXT_LANGUAGE = "key_next_language";
+ public static final String ITEM_SHOW_SETTINGS = "key_show_settings";
+
+ private final LinkedHashMap KEYS = new LinkedHashMap<>();
+ private final Collection items;
+ private final SettingsStore settings;
+
+ public SectionKeymap(Collection dropDowns, Context context, SettingsStore settings) {
+ items = dropDowns;
+ this.settings = settings;
+
+ Resources resources = context.getResources();
+
+ KEYS.put(String.valueOf(0), resources.getString(R.string.key_none));
+ if (KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK)) {
+ KEYS.put(String.valueOf(KeyEvent.KEYCODE_BACK), resources.getString(R.string.key_back));
+ KEYS.put(String.valueOf(KeyEvent.KEYCODE_CALL), resources.getString(R.string.key_call));
+ }
+ KEYS.put(
+ String.valueOf(-KeyEvent.KEYCODE_CALL),
+ resources.getString(R.string.key_call) + " " + resources.getString(R.string.key_hold_key)
+ );
+ if (KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_DEL)) {
+ KEYS.put(String.valueOf(KeyEvent.KEYCODE_DEL), resources.getString(R.string.key_delete));
+ KEYS.put(
+ String.valueOf(-KeyEvent.KEYCODE_DEL),
+ resources.getString(R.string.key_delete) + " " + resources.getString(R.string.key_hold_key)
+ );
+ }
+ if (KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_F1)) {
+ KEYS.put(String.valueOf(KeyEvent.KEYCODE_F1), resources.getString(R.string.key_f1));
+ KEYS.put(
+ String.valueOf(-KeyEvent.KEYCODE_F1),
+ resources.getString(R.string.key_f1) + " " + resources.getString(R.string.key_hold_key)
+ );
+ }
+ if (KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_F2)) {
+ KEYS.put(String.valueOf(KeyEvent.KEYCODE_F2), resources.getString(R.string.key_f2));
+ KEYS.put(
+ String.valueOf(-KeyEvent.KEYCODE_F2),
+ resources.getString(R.string.key_f2) + " " + resources.getString(R.string.key_hold_key)
+ );
+ }
+ if (KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_F3)) {
+ KEYS.put(String.valueOf(KeyEvent.KEYCODE_F3), resources.getString(R.string.key_f3));
+ KEYS.put(
+ String.valueOf(-KeyEvent.KEYCODE_F3),
+ resources.getString(R.string.key_f3) + " " + resources.getString(R.string.key_hold_key)
+ );
+ }
+ if (KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_F4)) {
+ KEYS.put(String.valueOf(KeyEvent.KEYCODE_F4), resources.getString(R.string.key_f4));
+ KEYS.put(
+ String.valueOf(-KeyEvent.KEYCODE_F4),
+ resources.getString(R.string.key_f4) + " " + resources.getString(R.string.key_hold_key)
+ );
+ }
+ if (ViewConfiguration.get(context).hasPermanentMenuKey()) {
+ KEYS.put(String.valueOf(KeyEvent.KEYCODE_MENU), resources.getString(R.string.key_menu));
+ KEYS.put(
+ String.valueOf(-KeyEvent.KEYCODE_MENU),
+ resources.getString(R.string.key_menu) + " " + resources.getString(R.string.key_hold_key)
+ );
+ }
+ KEYS.put(String.valueOf(KeyEvent.KEYCODE_POUND), resources.getString(R.string.key_pound));
+ KEYS.put(
+ String.valueOf(-KeyEvent.KEYCODE_POUND),
+ resources.getString(R.string.key_pound) + " " + resources.getString(R.string.key_hold_key)
+ );
+ KEYS.put(String.valueOf(KeyEvent.KEYCODE_STAR), resources.getString(R.string.key_star));
+ KEYS.put(
+ String.valueOf(-KeyEvent.KEYCODE_STAR),
+ resources.getString(R.string.key_star) + " " + resources.getString(R.string.key_hold_key)
+ );
+ }
+
+
+ public void reloadSettings() {
+ for (DropDownPreference dropDown : items) {
+ int keypadKey = settings.getFunctionKey(dropDown.getKey());
+ if (keypadKey != 0) {
+ dropDown.setValue(String.valueOf(keypadKey));
+ previewCurrentKey(dropDown);
+ }
+ }
+ }
+
+
+ public SectionKeymap populate() {
+ populateOtherItems(null);
+ return this;
+ }
+
+
+ public SectionKeymap activate() {
+ for (DropDownPreference item : items) {
+ onItemClick(item);
+ }
+
+ return this;
+ }
+
+
+ private void populateOtherItems(DropDownPreference itemToSkip) {
+ for (DropDownPreference item : items) {
+ if (itemToSkip != null && item != null && Objects.equals(itemToSkip.getKey(), item.getKey())) {
+ continue;
+ }
+ populateItem(item);
+ previewCurrentKey(item);
+ }
+ }
+
+
+ private void populateItem(DropDownPreference dropDown) {
+ if (dropDown == null) {
+ Logger.w("tt9/SectionKeymap.populateItem", "Cannot populate a NULL item. Ignoring.");
+ return;
+ }
+
+ ArrayList keys = new ArrayList<>();
+ for (String key : KEYS.keySet()) {
+ if (
+ validateKey(dropDown, String.valueOf(key))
+ // backspace works both when pressed short and long,
+ // so separate "hold" and "not hold" options for it make no sense
+ && !(dropDown.getKey().equals(ITEM_BACKSPACE) && Integer.parseInt(key) < 0)
+ // "show settings" must always be available for the users not to lose
+ // access to the Settings screen
+ && !(dropDown.getKey().equals(ITEM_SHOW_SETTINGS) && key.equals("0"))
+ ) {
+ keys.add(String.valueOf(key));
+ }
+ }
+
+ ArrayList values = new ArrayList<>();
+ for (String key : keys) {
+ values.add(KEYS.get(key));
+ }
+
+ dropDown.setEntries(values.toArray(new CharSequence[0]));
+ dropDown.setEntryValues(keys.toArray(new CharSequence[0]));
+ }
+
+
+ private void onItemClick(DropDownPreference item) {
+ if (item == null) {
+ Logger.w("tt9/SectionKeymap.populateItem", "Cannot set a click listener a NULL item. Ignoring.");
+ return;
+ }
+
+ item.setOnPreferenceChangeListener((preference, newKey) -> {
+ if (!validateKey((DropDownPreference) preference, newKey.toString())) {
+ return false;
+ }
+
+ ((DropDownPreference) preference).setValue(newKey.toString());
+ previewCurrentKey((DropDownPreference) preference, newKey.toString());
+ populateOtherItems((DropDownPreference) preference);
+ return true;
+ });
+ }
+
+
+ private void previewCurrentKey(DropDownPreference dropDown) {
+ previewCurrentKey(dropDown, dropDown.getValue());
+ }
+
+
+ private void previewCurrentKey(DropDownPreference dropDown, String key) {
+ if (dropDown == null) {
+ return;
+ }
+
+ dropDown.setSummary(KEYS.get(key));
+ }
+
+
+ private boolean validateKey(DropDownPreference dropDown, String key) {
+ if (dropDown == null || key == null) {
+ return false;
+ }
+
+ if (key.equals("0")) {
+ return true;
+ }
+
+ for (DropDownPreference item : items) {
+ if (item != null && !dropDown.getKey().equals(item.getKey()) && key.equals(item.getValue())) {
+ Logger.i("tt9/SectionKeymap.validateKey", "Key: '" + key + "' is already in use for function: " + item.getKey());
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/src/io/github/sspanak/tt9/preferences/T9Preferences.java b/src/io/github/sspanak/tt9/preferences/SettingsStore.java
similarity index 53%
rename from src/io/github/sspanak/tt9/preferences/T9Preferences.java
rename to src/io/github/sspanak/tt9/preferences/SettingsStore.java
index cc599544..1ea1c199 100644
--- a/src/io/github/sspanak/tt9/preferences/T9Preferences.java
+++ b/src/io/github/sspanak/tt9/preferences/SettingsStore.java
@@ -8,6 +8,9 @@ import androidx.preference.PreferenceManager;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
import io.github.sspanak.tt9.Logger;
import io.github.sspanak.tt9.ime.TraditionalT9;
@@ -15,24 +18,22 @@ import io.github.sspanak.tt9.ime.modes.InputMode;
import io.github.sspanak.tt9.languages.LanguageCollection;
-public class T9Preferences {
- public static final int MAX_LANGUAGES = 32;
-
- private static T9Preferences self;
+public class SettingsStore {
+ private static SettingsStore self;
private final SharedPreferences prefs;
private final SharedPreferences.Editor prefsEditor;
- public T9Preferences (Context context) {
+ public SettingsStore(Context context) {
prefs = PreferenceManager.getDefaultSharedPreferences(context);
prefsEditor = prefs.edit();
}
- public static T9Preferences getInstance() {
+ public static SettingsStore getInstance() {
if (self == null) {
- self = new T9Preferences(TraditionalT9.getMainContext());
+ self = new SettingsStore(TraditionalT9.getMainContext());
}
return self;
@@ -45,21 +46,12 @@ public class T9Preferences {
return LanguageCollection.getLanguage(langId) != null;
}
- private boolean isLanguageInRange(int langId) {
- return langId > 0 && langId <= MAX_LANGUAGES;
- }
-
private boolean validateSavedLanguage(int langId, String logTag) {
if (!doesLanguageExist(langId)) {
Logger.w(logTag, "Not saving invalid language with ID: " + langId);
return false;
}
- if (!isLanguageInRange(langId)) {
- Logger.w(logTag, "Valid language ID range is [0, 31]. Not saving out-of-range language: " + langId);
- return false;
- }
-
return true;
}
@@ -75,32 +67,47 @@ public class T9Preferences {
/************* input settings *************/
- public ArrayList getEnabledLanguages() {
- int languageMask = prefs.getInt("pref_enabled_languages", 1);
- ArrayListlanguageIds = new ArrayList<>();
+ public ArrayList getEnabledLanguageIds() {
+ Set languagesPref = getEnabledLanguagesIdsAsStrings();
- for (int langId = 1; langId < MAX_LANGUAGES; langId++) {
- int maskBit = 1 << (langId - 1);
- if ((maskBit & languageMask) != 0) {
- languageIds.add(langId);
- }
+ ArrayListlanguageIds = new ArrayList<>();
+ for (String languageId : languagesPref) {
+ languageIds.add(Integer.valueOf(languageId));
}
return languageIds;
}
- public void saveEnabledLanguages(ArrayList languageIds) {
- int languageMask = 0;
+ public Set getEnabledLanguagesIdsAsStrings() {
+ return prefs.getStringSet("pref_languages", new HashSet<>(Collections.singletonList("1")));
+ }
+
+ public void saveEnabledLanguageIds(ArrayList languageIds) {
+ Set idsAsStrings = new HashSet<>();
for (int langId : languageIds) {
- if (!validateSavedLanguage(langId, "tt9/saveEnabledLanguages")){
+ idsAsStrings.add(String.valueOf(langId));
+ }
+
+ saveEnabledLanguageIds(idsAsStrings);
+ }
+
+ public void saveEnabledLanguageIds(Set languageIds) {
+ Set validLanguageIds = new HashSet<>();
+
+ for (String langId : languageIds) {
+ if (!validateSavedLanguage(Integer.parseInt(langId), "tt9/saveEnabledLanguageIds")){
continue;
}
- int languageMaskBit = 1 << (langId - 1);
- languageMask |= languageMaskBit;
+ validLanguageIds.add(langId);
}
- prefsEditor.putInt("pref_enabled_languages", languageMask);
+ if (validLanguageIds.size() == 0) {
+ Logger.w("tt9/saveEnabledLanguageIds", "Refusing to save an empty language list");
+ return;
+ }
+
+ prefsEditor.putStringSet("pref_languages", validLanguageIds);
prefsEditor.apply();
}
@@ -151,14 +158,55 @@ public class T9Preferences {
}
- /************* hotkey settings *************/
+ /************* function key settings *************/
+
+ public boolean areFunctionKeysSet() {
+ return getKeyShowSettings() != 0;
+ }
+
+ public void setDefaultKeys() {
+ prefsEditor.putString(SectionKeymap.ITEM_ADD_WORD, String.valueOf(KeyEvent.KEYCODE_STAR));
+ prefsEditor.putString(SectionKeymap.ITEM_BACKSPACE, String.valueOf(KeyEvent.KEYCODE_BACK));
+ prefsEditor.putString(SectionKeymap.ITEM_NEXT_INPUT_MODE, String.valueOf(KeyEvent.KEYCODE_POUND));
+ prefsEditor.putString(SectionKeymap.ITEM_NEXT_LANGUAGE, String.valueOf(-KeyEvent.KEYCODE_POUND));
+ prefsEditor.putString(SectionKeymap.ITEM_SHOW_SETTINGS, String.valueOf(-KeyEvent.KEYCODE_STAR));
+ prefsEditor.apply();
+ }
+
+ public int getFunctionKey(String functionName) {
+ try {
+ return Integer.parseInt(prefs.getString(functionName, "0"));
+ } catch (NumberFormatException e) {
+ return 0;
+ }
+ }
+
+ public int getKeyAddWord() {
+ return getFunctionKey(SectionKeymap.ITEM_ADD_WORD);
+ }
public int getKeyBackspace() {
- return prefs.getInt("pref_key_backspace", KeyEvent.KEYCODE_BACK);
+ return getFunctionKey(SectionKeymap.ITEM_BACKSPACE);
}
- public int getKeyInputMode() { return prefs.getInt("pref_key_input_mode", KeyEvent.KEYCODE_POUND); }
- public int getKeyOtherActions() { return prefs.getInt("pref_key_other_actions", KeyEvent.KEYCODE_STAR); }
+ public int getKeyNextInputMode() {
+ return getFunctionKey(SectionKeymap.ITEM_NEXT_INPUT_MODE);
+ }
+
+ public int getKeyNextLanguage() {
+ return getFunctionKey(SectionKeymap.ITEM_NEXT_LANGUAGE);
+ }
+
+ public int getKeyShowSettings() {
+ return getFunctionKey(SectionKeymap.ITEM_SHOW_SETTINGS);
+ }
+
+ /************* UI settings *************/
+
+ public boolean getDarkTheme() { return prefs.getBoolean("pref_dark_theme", true); }
+ public void setDarkTheme(boolean yes) { prefsEditor.putBoolean("pref_dark_theme", yes); }
+
+ public boolean getShowSoftKeys() { return prefs.getBoolean("pref_show_soft_keys", true); }
/************* internal settings *************/
@@ -177,7 +225,7 @@ public class T9Preferences {
}
public void saveLastWord(String lastWord) {
- // "last_word" was part of the original Preferences implementation.
+ // "last_word" was part of the original Settings implementation.
// It is weird, but it is simple and it works, so I decided to keep it.
prefsEditor.putString("last_word", lastWord);
prefsEditor.apply();
diff --git a/src/io/github/sspanak/tt9/settings_legacy/CustomInflater.java b/src/io/github/sspanak/tt9/settings_legacy/CustomInflater.java
deleted file mode 100644
index 292b5309..00000000
--- a/src/io/github/sspanak/tt9/settings_legacy/CustomInflater.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package io.github.sspanak.tt9.settings_legacy;
-
-// http://stackoverflow.com/a/8488691
-
-import android.content.Context;
-import android.content.res.XmlResourceParser;
-import android.util.AttributeSet;
-
-import org.xmlpull.v1.XmlPullParser;
-
-import java.lang.reflect.Constructor;
-import java.util.ArrayList;
-
-public class CustomInflater {
- public static ArrayList inflate(Context context, int xmlFileResId, Object[] isettings)
- throws Exception {
- ArrayList settings = new ArrayList();
-
- XmlResourceParser parser = context.getResources().getXml(xmlFileResId);
- int token;
- while ((token = parser.next()) != XmlPullParser.END_DOCUMENT) {
- if (token == XmlPullParser.START_TAG) {
- if (!parser.getName().equals("Settings")) {
- //prepend package
- Class aClass = Class.forName("io.github.sspanak.tt9.settings_legacy."+parser.getName());
- Class>[] params = new Class[]{Context.class, AttributeSet.class, isettings.getClass()};
- Constructor> constructor = aClass.getConstructor(params);
- try {
- settings.add((Setting) constructor.newInstance(context, parser, isettings));
- } catch (InstantiationException e) {
- e.printStackTrace();
- }
- }
- }
- }
-
- return settings;
- }
-}
diff --git a/src/io/github/sspanak/tt9/settings_legacy/Setting.java b/src/io/github/sspanak/tt9/settings_legacy/Setting.java
deleted file mode 100644
index ce6cbe5a..00000000
--- a/src/io/github/sspanak/tt9/settings_legacy/Setting.java
+++ /dev/null
@@ -1,42 +0,0 @@
-package io.github.sspanak.tt9.settings_legacy;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-
-import io.github.sspanak.tt9.R;
-
-public class Setting {
- String title;
- String summary = null;
- public String id;
- public int widgetID = 0;
- public int layout;
- protected View view;
-
- public Setting (Context context, AttributeSet attrs, Object[] isettings) {
- // http://stackoverflow.com/a/8488691
- for (int i = 0; i < attrs.getAttributeCount(); i++) {
- String attr = attrs.getAttributeName(i);
- if ("title".equals(attr)) {
- // load string resource
- title = context.getString(attrs.getAttributeResourceValue(i, 0));
- } else if ("summary".equals(attr)) {
- summary = context.getString(attrs.getAttributeResourceValue(i, 0));
- } else if ("id".equals(attr)){
- id = attrs.getAttributeValue(i);
- }
- }
- if (summary == null)
- layout = R.layout.setting;
- else
- layout = R.layout.setting_sum;
- }
-
- public void clicked(final Context context) {}
-
- public void setView(View view) {
- this.view = view;
- }
- public void init() {};
-}
diff --git a/src/io/github/sspanak/tt9/settings_legacy/SettingAdapter.java b/src/io/github/sspanak/tt9/settings_legacy/SettingAdapter.java
deleted file mode 100644
index 294680da..00000000
--- a/src/io/github/sspanak/tt9/settings_legacy/SettingAdapter.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package io.github.sspanak.tt9.settings_legacy;
-
-// https://github.com/codepath/android_guides/wiki/Using-an-ArrayAdapter-with-ListView
-
-import android.content.Context;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
-import android.widget.TextView;
-
-import io.github.sspanak.tt9.R;
-
-import java.util.ArrayList;
-
-public class SettingAdapter extends ArrayAdapter {
- public SettingAdapter(Context context, ArrayList settings) {
- super(context, 0, settings);
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- // Get the data item for this position
- Setting setting = getItem(position);
- final LayoutInflater layoutInflater = LayoutInflater.from(getContext());
- // Check if an existing view is being reused, otherwise inflate the view
- if (convertView == null) {
- convertView = layoutInflater.inflate(R.layout.setting_widget, parent, false);
- }
- setting.setView(convertView);
- // Lookup view for data population
- ((TextView) convertView.findViewById(R.id.title)).setText(setting.title);
- View sv = convertView.findViewById(R.id.summary);
- if (setting.summary != null && sv != null) {
- ((TextView) sv).setText(setting.summary);
- sv.setVisibility(View.VISIBLE);
- }
- else if (sv != null) { sv.setVisibility(View.GONE); }
-
- final ViewGroup widgetFrame = (ViewGroup) convertView.findViewById(R.id.widget_frame);
- if (setting.widgetID != 0) {
- widgetFrame.removeAllViews();
- layoutInflater.inflate(setting.widgetID, widgetFrame);
- widgetFrame.setVisibility(View.VISIBLE);
- }
- else {
- // hide the widget area
- widgetFrame.setVisibility(View.GONE);
- }
- setting.init();
- // Return the completed view to render on screen
- return convertView;
- }
-}
diff --git a/src/io/github/sspanak/tt9/settings_legacy/SettingList.java b/src/io/github/sspanak/tt9/settings_legacy/SettingList.java
deleted file mode 100644
index 434df602..00000000
--- a/src/io/github/sspanak/tt9/settings_legacy/SettingList.java
+++ /dev/null
@@ -1,56 +0,0 @@
-package io.github.sspanak.tt9.settings_legacy;
-
-import android.app.AlertDialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.util.AttributeSet;
-
-import io.github.sspanak.tt9.R;
-
-public class SettingList extends Setting {
- String[] entries;
- int[] entryValues;
- int defaultValue;
- int value;
-
- public SettingList (Context context, AttributeSet attrs, Object[] isettings) {
- super(context, attrs, isettings);
- // http://stackoverflow.com/a/8488691
- for (int i = 0; i < attrs.getAttributeCount(); i++) {
- String attr = attrs.getAttributeName(i);
- if ("defaultValue".equals(attr)) {
- defaultValue = attrs.getAttributeIntValue(i, -1);
- } else if ("entryValues".equals(attr)) {
- // load string resource
- entryValues = context.getResources().getIntArray(attrs.getAttributeResourceValue(i, 0));
- } else if ("entries".equals(attr)) {
- entries = context.getResources().getStringArray(attrs.getAttributeResourceValue(i, 0));
- }
- }
-
- widgetID = R.layout.preference_dialog;
- layout = R.layout.setting_widget;
- }
-
- public void clicked(final Context context) {
- AlertDialog.Builder builderSingle = new AlertDialog.Builder(context);
- builderSingle.setTitle(title);
-
- builderSingle.setNegativeButton(android.R.string.cancel,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- dialog.dismiss();
- }
- });
- builderSingle.setSingleChoiceItems(entries, value,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- value = entryValues[which];
- dialog.dismiss();
- }
- });
- builderSingle.show();
- }
-}
diff --git a/src/io/github/sspanak/tt9/settings_legacy/SettingMultiList.java b/src/io/github/sspanak/tt9/settings_legacy/SettingMultiList.java
deleted file mode 100644
index 796f5a64..00000000
--- a/src/io/github/sspanak/tt9/settings_legacy/SettingMultiList.java
+++ /dev/null
@@ -1,84 +0,0 @@
-package io.github.sspanak.tt9.settings_legacy;
-
-import android.app.AlertDialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.util.AttributeSet;
-import android.widget.TextView;
-
-import java.util.ArrayList;
-
-import io.github.sspanak.tt9.R;
-import io.github.sspanak.tt9.preferences.T9Preferences;
-
-public class SettingMultiList extends SettingList {
- boolean[] selectedEntries;
-
- public SettingMultiList (Context context, AttributeSet attrs, Object[] isettings) {
- super(context, attrs, isettings);
- selectedEntries = new boolean[entries.length];
- for (int langId : T9Preferences.getInstance().getEnabledLanguages()) {
- selectedEntries[langId - 1] = true; // languages are 1-based, unlike arrays
- }
- summary = buildItems();
- }
-
- public void clicked(final Context context) {
- AlertDialog.Builder builderMulti = new AlertDialog.Builder(context);
- builderMulti.setTitle(title);
-
- builderMulti.setNegativeButton(android.R.string.cancel,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- dialog.dismiss();
- }
- });
- builderMulti.setPositiveButton(android.R.string.ok,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- if (id.equals("pref_lang_support")) {
- T9Preferences.getInstance().saveEnabledLanguages(buildSelection());
- }
- summary = buildItems();
- dialog.dismiss();
- ((TextView)view.findViewById(R.id.summary)).setText(summary);
- }
- });
- builderMulti.setMultiChoiceItems(entries, selectedEntries,
- new DialogInterface.OnMultiChoiceClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which, boolean opt) {
- selectedEntries[which] = opt;
- }
- });
- builderMulti.show();
- }
-
- private ArrayList buildSelection(){
- ArrayList selection = new ArrayList<>();
- for (int x=0;x 1)
- sb.setLength(sb.length()-2);
- return sb.toString();
- }
-}
diff --git a/src/io/github/sspanak/tt9/ui/AddWordAct.java b/src/io/github/sspanak/tt9/ui/AddWordAct.java
index decf3466..74569e6f 100644
--- a/src/io/github/sspanak/tt9/ui/AddWordAct.java
+++ b/src/io/github/sspanak/tt9/ui/AddWordAct.java
@@ -1,6 +1,5 @@
package io.github.sspanak.tt9.ui;
-import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
@@ -9,23 +8,31 @@ import android.os.Message;
import android.view.View;
import android.widget.EditText;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.app.AppCompatDelegate;
+
import io.github.sspanak.tt9.Logger;
import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.db.DictionaryDb;
import io.github.sspanak.tt9.db.InsertBlankWordException;
import io.github.sspanak.tt9.languages.InvalidLanguageException;
import io.github.sspanak.tt9.languages.LanguageCollection;
-import io.github.sspanak.tt9.preferences.T9Preferences;
+import io.github.sspanak.tt9.preferences.SettingsStore;
-public class AddWordAct extends Activity {
+public class AddWordAct extends AppCompatActivity {
- View main;
- int lang;
- String word;
+ private View main;
+ private int lang;
+ private String word;
@Override
protected void onCreate(Bundle savedData) {
+ AppCompatDelegate.setDefaultNightMode(
+ SettingsStore.getInstance().getDarkTheme() ? AppCompatDelegate.MODE_NIGHT_YES : AppCompatDelegate.MODE_NIGHT_NO
+ );
+
super.onCreate(savedData);
+
Intent i = getIntent();
word = i.getStringExtra("io.github.sspanak.tt9.word");
lang = i.getIntExtra("io.github.sspanak.tt9.lang", -1);
@@ -46,7 +53,7 @@ public class AddWordAct extends Activity {
switch (msg.what) {
case 0:
Logger.d("onAddedWord", "Added word: '" + word + "'...");
- T9Preferences.getInstance().saveLastWord(word);
+ SettingsStore.getInstance().saveLastWord(word);
break;
case 1:
diff --git a/src/io/github/sspanak/tt9/ui/DictionaryLoadingBar.java b/src/io/github/sspanak/tt9/ui/DictionaryLoadingBar.java
index eead0dc8..eff79216 100644
--- a/src/io/github/sspanak/tt9/ui/DictionaryLoadingBar.java
+++ b/src/io/github/sspanak/tt9/ui/DictionaryLoadingBar.java
@@ -21,6 +21,8 @@ import io.github.sspanak.tt9.languages.LanguageCollection;
public class DictionaryLoadingBar {
+ private static DictionaryLoadingBar self;
+
private static final int NOTIFICATION_ID = 1;
private static final String NOTIFICATION_CHANNEL_ID = "loading-notifications";
@@ -33,7 +35,16 @@ public class DictionaryLoadingBar {
private boolean hasFailed = false;
- DictionaryLoadingBar(Context context) {
+ public static DictionaryLoadingBar getInstance(Context context) {
+ if (self == null) {
+ self = new DictionaryLoadingBar(context);
+ }
+
+ return self;
+ }
+
+
+ public DictionaryLoadingBar(Context context) {
resources = context.getResources();
manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
@@ -46,6 +57,7 @@ public class DictionaryLoadingBar {
));
notificationBuilder = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID);
} else {
+ //noinspection deprecation
notificationBuilder = new NotificationCompat.Builder(context);
}
diff --git a/src/io/github/sspanak/tt9/ui/SuggestionsAdapter.java b/src/io/github/sspanak/tt9/ui/SuggestionsAdapter.java
index da56430d..e97db26e 100644
--- a/src/io/github/sspanak/tt9/ui/SuggestionsAdapter.java
+++ b/src/io/github/sspanak/tt9/ui/SuggestionsAdapter.java
@@ -13,17 +13,17 @@ import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
public class SuggestionsAdapter extends RecyclerView.Adapter {
- private final int colorHighlight;
private final int layout;
private final int textViewResourceId;
private final LayoutInflater mInflater;
private final List mSuggestions;
+ private int colorDefault;
+ private int colorHighlight;
private int selectedIndex = 0;
- public SuggestionsAdapter(Context context, int layout, int textViewResourceId, int highLightColor, List suggestions) {
- this.colorHighlight = highLightColor;
+ public SuggestionsAdapter(Context context, int layout, int textViewResourceId, List suggestions) {
this.layout = layout;
this.textViewResourceId = textViewResourceId;
this.mInflater = LayoutInflater.from(context);
@@ -41,6 +41,7 @@ public class SuggestionsAdapter extends RecyclerView.Adapter suggestions = new ArrayList<>();
@@ -40,8 +40,8 @@ public class SuggestionsView {
private void configureAnimation() {
DefaultItemAnimator animator = new DefaultItemAnimator();
- int translateDuration = T9Preferences.getInstance().getSuggestionTranslateAnimationDuration();
- int selectDuration = T9Preferences.getInstance().getSuggestionSelectAnimationDuration();
+ int translateDuration = SettingsStore.getInstance().getSuggestionTranslateAnimationDuration();
+ int selectDuration = SettingsStore.getInstance().getSuggestionSelectAnimationDuration();
animator.setMoveDuration(selectDuration);
animator.setChangeDuration(translateDuration);
@@ -57,10 +57,11 @@ public class SuggestionsView {
context,
R.layout.suggestion_list_view,
R.id.suggestion_list_item,
- ContextCompat.getColor(context, R.color.candidate_selected),
suggestions
);
mView.setAdapter(mSuggestionsAdapter);
+
+ setDarkTheme(true); // just use some default colors
}
@@ -134,4 +135,28 @@ public class SuggestionsView {
mView.scrollToPosition(selectedIndex);
}
+
+
+ /**
+ * setDarkTheme
+ * Changes the suggestion colors according to the theme.
+ *
+ * We need to do this manually, instead of relying on the Context to resolve the appropriate colors,
+ * because this View is part of the main service View. And service Views are always locked to the
+ * system context and theme.
+ *
+ * More info:
+ * https://stackoverflow.com/questions/72382886/system-applies-night-mode-to-views-added-in-service-type-application-overlay
+ */
+ public void setDarkTheme(boolean darkEnabled) {
+ Context context = mView.getContext();
+
+ int backgroundColor = darkEnabled ? R.color.dark_candidate_background : R.color.candidate_background;
+ int defaultColor = darkEnabled ? R.color.dark_candidate_color : R.color.candidate_color;
+ int highlightColor = darkEnabled ? R.color.dark_candidate_selected : R.color.candidate_selected;
+
+ mView.setBackgroundColor(ContextCompat.getColor(context, backgroundColor));
+ mSuggestionsAdapter.setColorDefault(ContextCompat.getColor(context, defaultColor));
+ mSuggestionsAdapter.setColorHighlight(ContextCompat.getColor(context, highlightColor));
+ }
}
diff --git a/src/io/github/sspanak/tt9/ui/TraditionalT9Settings.java b/src/io/github/sspanak/tt9/ui/TraditionalT9Settings.java
deleted file mode 100644
index fceca14f..00000000
--- a/src/io/github/sspanak/tt9/ui/TraditionalT9Settings.java
+++ /dev/null
@@ -1,134 +0,0 @@
-package io.github.sspanak.tt9.ui;
-
-import android.app.ListActivity;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.view.View;
-import android.widget.ListAdapter;
-import android.widget.ListView;
-
-import java.util.ArrayList;
-
-import io.github.sspanak.tt9.R;
-import io.github.sspanak.tt9.db.DictionaryDb;
-import io.github.sspanak.tt9.db.DictionaryImportAlreadyRunningException;
-import io.github.sspanak.tt9.db.DictionaryLoader;
-import io.github.sspanak.tt9.languages.Language;
-import io.github.sspanak.tt9.languages.LanguageCollection;
-import io.github.sspanak.tt9.preferences.T9Preferences;
-import io.github.sspanak.tt9.settings_legacy.CustomInflater;
-import io.github.sspanak.tt9.settings_legacy.Setting;
-import io.github.sspanak.tt9.settings_legacy.SettingAdapter;
-
-public class TraditionalT9Settings extends ListActivity implements DialogInterface.OnCancelListener {
-
- private DictionaryLoader loader;
- DictionaryLoadingBar progressBar;
-
- Context mContext = null;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- progressBar = new DictionaryLoadingBar(this);
-
- // get settings
- T9Preferences prefs = new T9Preferences(getApplicationContext());
- Object[] settings = {
- prefs.getInputMode()
- };
- ListAdapter settingitems;
- try {
- settingitems = new SettingAdapter(this, CustomInflater.inflate(this, R.xml.prefs, settings));
- } catch (Exception e) {
- e.printStackTrace();
- return;
- }
- setContentView(R.layout.preference_list_content);
- setListAdapter(settingitems);
- mContext = this;
-
- }
-
- @Override
- public void onCancel(DialogInterface dint) {
- if (loader != null) {
- loader.stop();
- }
- }
-
- @Override
- protected void onListItemClick(ListView l, View v, int position, long id) {
- Setting s = (Setting)getListView().getItemAtPosition(position);
- switch (s.id) {
- case "help":
- openHelp();
- break;
- case "loaddict":
- loadDictionaries();
- break;
- case "truncatedict":
- truncateWords();
- break;
- default:
- s.clicked(mContext);
- break;
- }
- }
-
-
- private void openHelp() {
- Intent i = new Intent(Intent.ACTION_VIEW);
- i.setData(Uri.parse(getString(R.string.help_url)));
- startActivity(i);
- }
-
- private void truncateWords() {
- if (loader != null && loader.isRunning()) {
- loader.stop();
- }
-
- Handler afterTruncate = new Handler(Looper.getMainLooper()) {
- @Override
- public void handleMessage(Message msg) {
- UI.toast(mContext, R.string.dictionary_truncated);
- }
- };
- DictionaryDb.truncateWords(afterTruncate);
- }
-
- private void loadDictionaries() {
- ArrayList languages = LanguageCollection.getAll(T9Preferences.getInstance().getEnabledLanguages());
- progressBar.setFileCount(languages.size());
-
- Handler loadHandler = new Handler(Looper.getMainLooper()) {
- @Override
- public void handleMessage(Message msg) {
- progressBar.show(msg.getData());
- if (progressBar.isCompleted()) {
- UI.toast(mContext, R.string.dictionary_loaded);
- } else if (progressBar.isFailed()) {
- UI.toast(mContext, R.string.dictionary_load_failed);
- }
- }
- };
-
- if (loader == null) {
- loader = new DictionaryLoader(this);
- }
-
- try {
- loader.load(loadHandler, languages);
- } catch (DictionaryImportAlreadyRunningException e) {
- loader.stop();
- UI.toast(this, getString(R.string.dictionary_load_cancelled));
- }
- }
-}
diff --git a/src/io/github/sspanak/tt9/ui/UI.java b/src/io/github/sspanak/tt9/ui/UI.java
index e3476ba2..7c202080 100644
--- a/src/io/github/sspanak/tt9/ui/UI.java
+++ b/src/io/github/sspanak/tt9/ui/UI.java
@@ -9,6 +9,7 @@ import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.ime.TraditionalT9;
import io.github.sspanak.tt9.ime.modes.InputMode;
import io.github.sspanak.tt9.languages.Language;
+import io.github.sspanak.tt9.preferences.PreferencesActivity;
public class UI {
public static void showAddWordDialog(TraditionalT9 tt9, int language, String currentWord) {
@@ -21,8 +22,8 @@ public class UI {
}
- public static void showPreferencesScreen(TraditionalT9 tt9) {
- Intent prefIntent = new Intent(tt9, TraditionalT9Settings.class);
+ public static void showSettingsScreen(TraditionalT9 tt9) {
+ Intent prefIntent = new Intent(tt9, PreferencesActivity.class);
prefIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
prefIntent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
tt9.hideWindow();
diff --git a/src/pl/wavesoftware/widget/MultiSelectListPreference.java b/src/pl/wavesoftware/widget/MultiSelectListPreference.java
deleted file mode 100644
index 84b79919..00000000
--- a/src/pl/wavesoftware/widget/MultiSelectListPreference.java
+++ /dev/null
@@ -1,177 +0,0 @@
-package pl.wavesoftware.widget;
-// https://gist.github.com/cardil/4754571/07b4b6ffd37b440bbdec2cafa1ab7411c5ad3873
-// modified to work specifically for this service
-
-import android.app.AlertDialog.Builder;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnMultiChoiceClickListener;
-import android.content.res.TypedArray;
-import android.preference.ListPreference;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-import io.github.sspanak.tt9.Logger;
-
-public class MultiSelectListPreference extends ListPreference {
-
- private String separator;
- private static final String DEFAULT_SEPARATOR = "|";
- private boolean[] entryChecked;
-
- public MultiSelectListPreference(Context context, AttributeSet attributeSet) {
- super(context, attributeSet);
- entryChecked = new boolean[getEntries().length];
- separator = DEFAULT_SEPARATOR;
- }
-
- public MultiSelectListPreference(Context context) {
- this(context, null);
- }
-
- @Override
- protected void onPrepareDialogBuilder(Builder builder) {
- CharSequence[] entries = getEntries();
- CharSequence[] entryValues = getEntryValues();
- if (entries == null || entryValues == null
- || entries.length != entryValues.length) {
- throw new IllegalStateException(
- "MultiSelectListPreference requires an entries array and an entryValues "
- + "array which are both the same length");
- }
-
- restoreCheckedEntries();
- OnMultiChoiceClickListener listener = new DialogInterface.OnMultiChoiceClickListener() {
- public void onClick(DialogInterface dialog, int which, boolean val) {
- entryChecked[which] = val;
- }
- };
- builder.setMultiChoiceItems(entries, entryChecked, listener);
- }
-
- private CharSequence[] unpack(CharSequence val) {
- if (val == null || "".equals(val)) {
- return new CharSequence[0];
- } else {
- return ((String) val).split("\\"+separator);
- }
- }
-
- // added method
- public static int[] defaultunpack2Int(CharSequence val) {
- if (val == null || "".equals(val)) {
- //Logger.w("MultiSelectPref.defaultunpack", "val is null or empty");
- return new int[] {0}; //default pref
- } else {
- String[] sa = ((String) val).split("\\"+DEFAULT_SEPARATOR);
- if (sa.length < 1) {
- Logger.w("MSLPref.defaultunpack", "split is less than 1");
- return new int[] {0}; //default pref
- }
- int[] ia = new int[sa.length];
- for (int x=0;x valuesList = Arrays.asList(vals);
- for (int i = 0; i < entryValues.length; i++) {
- CharSequence entry = entryValues[i];
- entryChecked[i] = valuesList.contains(entry);
- }
- }
- }
-
- @Override
- protected void onDialogClosed(boolean positiveResult) {
- List values = new ArrayList();
-
- CharSequence[] entryValues = getEntryValues();
- if (positiveResult && entryValues != null) {
- for (int i = 0; i < entryValues.length; i++) {
- if (entryChecked[i] == true) {
- String val = (String) entryValues[i];
- values.add(val);
- }
- }
-
- String value = TextUtils.join(separator, values);
- setSummary(prepareSummary(values));
- setValueAndEvent(value);
- }
- }
-
- //modified to make sure some sane thing gets stored
- private void setValueAndEvent(String value) {
- if (callChangeListener(unpack(value))) {
- if (value == null || value.length() < 1) {
- setValue("0"); //default
- } else {
- setValue(value);
- }
- }
- }
-
- private CharSequence prepareSummary(List joined) {
- List titles = new ArrayList();
- CharSequence[] entryTitle = getEntries();
- CharSequence[] entryValues = getEntryValues();
- int ix = 0;
- for (CharSequence value : entryValues) {
- if (joined.contains(value)) {
- titles.add((String) entryTitle[ix]);
- }
- ix += 1;
- }
- return TextUtils.join(", ", titles);
- }
-
- @Override
- protected Object onGetDefaultValue(TypedArray typedArray, int index) {
- return typedArray.getTextArray(index);
- }
-
- @Override
- protected void onSetInitialValue(boolean restoreValue,
- Object rawDefaultValue) {
- String value = null;
- CharSequence[] defaultValue;
- if (rawDefaultValue == null) {
- defaultValue = new CharSequence[] {"0"};
- } else {
- defaultValue = (CharSequence[]) rawDefaultValue;
- }
- String joinedDefaultValue = TextUtils.join(separator, defaultValue);
- if (restoreValue) {
- value = getPersistedString(joinedDefaultValue);
- } else {
- value = joinedDefaultValue;
- }
-
- setSummary(prepareSummary(Arrays.asList(unpack(value))));
- setValueAndEvent(value);
- }
-
-}