1
0
Fork 0

New Settings screen

* Settings screen is now based on the Android SharedPreferences

* Added function key configuration on the Settings screen

* Added a setting for toggling the on-screen buttons

* Added a dark/light theme setting

* Improved translations

* Fixed a problem with launching the Settings screen directly from the Android settings

* Fixed ignoring keys not actually ignoring them properly
This commit is contained in:
sspanak 2022-11-08 15:13:28 +02:00 committed by Dimo Karaivanov
parent 4e59d3393c
commit b550d5d5dd
84 changed files with 1463 additions and 1205 deletions

View file

@ -6,7 +6,6 @@
xmlns:android="http://schemas.android.com/apk/res/android"> xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-configuration android:reqFiveWayNav="true" android:reqHardKeyboard="true" android:reqKeyboardType="qwerty"/> <uses-configuration android:reqFiveWayNav="true" android:reqHardKeyboard="true" android:reqKeyboardType="qwerty"/>
<uses-configuration android:reqFiveWayNav="true" android:reqHardKeyboard="true" android:reqKeyboardType="twelvekey"/> <uses-configuration android:reqFiveWayNav="true" android:reqHardKeyboard="true" android:reqKeyboardType="twelvekey"/>
@ -14,16 +13,18 @@
android:allowBackup="false" android:allowBackup="false"
android:icon="@drawable/ic_launcher" android:icon="@drawable/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:theme="@style/AppTheme"> android:theme="@style/Theme.AppCompat.DayNight">
<service android:name="io.github.sspanak.tt9.ime.TraditionalT9" android:permission="android.permission.BIND_INPUT_METHOD"> <service android:name="io.github.sspanak.tt9.ime.TraditionalT9" android:permission="android.permission.BIND_INPUT_METHOD"
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.view.InputMethod"/> <action android:name="android.view.InputMethod"/>
</intent-filter> </intent-filter>
<meta-data android:name="android.view.im" android:resource="@xml/method"/> <meta-data android:name="android.view.im" android:resource="@xml/method"/>
</service> </service>
<activity android:label="@string/app_settings" android:name="io.github.sspanak.tt9.ui.TraditionalT9Settings"> <activity android:label="@string/app_settings" android:name="io.github.sspanak.tt9.preferences.PreferencesActivity"
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN"/>
</intent-filter> </intent-filter>
@ -33,6 +34,6 @@
android:excludeFromRecents="true" android:excludeFromRecents="true"
android:label="@string/add_word_title" android:label="@string/add_word_title"
android:name="io.github.sspanak.tt9.ui.AddWordAct" android:name="io.github.sspanak.tt9.ui.AddWordAct"
android:theme="@android:style/Theme.Dialog"/> android:theme="@style/Theme.AppCompat.DayNight.Dialog"/>
</application> </application>
</manifest> </manifest>

76
CONTRIBUTING.md Normal file
View file

@ -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!

View file

@ -1,5 +1,5 @@
# Traditional T9 # 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-. 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). 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 ## 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 ## Contributing to the Project
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. 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.
### Building a Debug .apk - Adding new translations or fixing incorrect ones.
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. - 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).
- _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)
## License ## License
- The source code, the logo image and the icons are licensed under the conditions described in [LICENSE.txt](LICENSE.txt). - 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) - [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). - "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). - "Negotiate" and "Vibrocentric" fonts are under [The Fontspring Desktop/Ebook Font End User License](docs/desktop-ebook-EULA-1.8.txt).

View file

@ -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). All Source code and documentation are available on Github: [https://github.com/sspanak/tt9](https://github.com/sspanak/tt9).
## Initial Setup ## 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. - Go to Android Settings → System → Languages → Keyboards.
- Add Traditional T9 IME. - Add Traditional T9 IME.
@ -56,26 +56,32 @@ _Predictive mode only._
- **In 123 mode:** type the respective number. - **In 123 mode:** type the respective number.
- **In ABC and Predictive mode:** type a letter/punctuation character or hold to 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/#): #### Add Word Key (Default: Press ✱):
- **Short press:** Cycle input modes (abc → ABC → Predictive → 123) Add a new word to the dictionary for the current language.
- **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.
#### Other Actions Key (Star/✱): #### Backspace Key (Default: Press ↩ / Back):
- **Short press:** Add a new word to the dictionary. Just deletes text.
- **Long press:** Open the Configration screen.
#### Backspace Key (Back/↩): **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:
- 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:
- **Short Press when there is no text**: Go back to the previous screen (the system default action). - **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. - **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). - **Long Press**: Whatever the system default action is (i.e. show running applications list).
## On-screen soft keys All this does not apply, when using other keys. They will just delete text
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.
#### 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: #### Left Soft Key:
Open the [Settings screen](#settings-screen). Open the [Settings screen](#settings-screen).
@ -84,18 +90,18 @@ Open the [Settings screen](#settings-screen).
Backspace. Backspace.
## Settings Screen ## 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: To access it:
- Start typing in a text field to wake up TT9. - 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 ## 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 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) - [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). - "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 ## Privacy Policy
Traditional T9 does not collect any information about you or about the way you are using using the application. Traditional T9 does not collect any information about you or about the way you are using using the application.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 314 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 350 B

View file

@ -1,31 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- taken from Sharp 007SH -->
<selector
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_window_focused="false"
android:state_enabled="true"
android:drawable="@drawable/btn_circle_normal" />
<item
android:state_window_focused="false"
android:state_enabled="false"
android:drawable="@drawable/btn_circle_disable" />
<item
android:state_enabled="false"
android:state_pressed="true"
android:drawable="@drawable/btn_circle_disable" />
<item
android:state_pressed="true"
android:drawable="@drawable/btn_circle_pressed" />
<item
android:state_focused="true"
android:state_enabled="true"
android:drawable="@drawable/btn_circle_selected" />
<item
android:state_enabled="true"
android:drawable="@drawable/btn_circle_normal" />
<item
android:state_focused="true"
android:drawable="@drawable/btn_circle_disable_focused" />
<item android:drawable="@drawable/btn_circle_disable" />
</selector>

View file

@ -1,9 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" <shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle"> android:shape="rectangle">
<!-- Gradient Bg for bootan -->
<gradient <gradient
android:startColor="#424542" android:startColor="#929492"
android:endColor="#A5A2A5" android:endColor="#E3E7E3"
android:angle="90" /> android:angle="90" />
</shape> </shape>

View file

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/button_grad_press"
android:state_pressed="true" />
<item android:drawable="@drawable/button_grad"
android:state_focused="true" />
<item android:drawable="@drawable/button_grad" />
</selector>

View file

@ -1,9 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" <shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle"> android:shape="rectangle">
<!-- Gradient Bg for button -->
<gradient <gradient
android:startColor="#8C8E8C" android:startColor="#686C68"
android:endColor="#636163" android:endColor="#B8BCB8"
android:angle="90" /> android:angle="90" />
</shape> </shape>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:startColor="#2E322E"
android:endColor="#878B87"
android:angle="90" />
</shape>

View file

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- taken from Sharp 007SH -->
<selector
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_enabled="false"
android:drawable="@drawable/ic_btn_round_more_disabled" />
<item android:drawable="@drawable/ic_btn_round_more_normal" />
</selector>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" > <shape xmlns:android="http://schemas.android.com/apk/res/android" >
<size android:width="1dip" /> <size android:width="1px" />
<solid android:color="@color/candidate_separator" /> <solid android:color="@color/candidate_separator" />
</shape> </shape>

View file

@ -1,26 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- taken from android source -->
<!-- Copyright (C) 2006 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!-- Layout used by CheckBoxPreference for the checkbox style. This is inflated
inside android.R.layout.preference. -->
<CheckBox xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="4dip"
android:layout_gravity="center_vertical"
android:focusable="false"
android:clickable="false" />

View file

@ -8,63 +8,61 @@
android:id="@+id/main_suggestions_list" android:id="@+id/main_suggestions_list"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="@dimen/candidate_list_height" android:layout_height="@dimen/candidate_list_height"
android:fadingEdge="horizontal"
android:orientation="horizontal" android:orientation="horizontal"
android:scrollbars="none" android:paddingTop="1px"
android:background="@color/candidate_background" /> android:scrollbars="none" />
<LinearLayout <LinearLayout
android:id="@+id/main_soft_keys"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/soft_key_height" android:layout_height="@dimen/soft_key_height"
android:background="@drawable/bggradient"
android:baselineAligned="true" android:baselineAligned="true"
android:orientation="horizontal"> android:orientation="horizontal">
<Button <Button
android:id="@+id/main_left" android:id="@+id/main_left"
style="@android:style/Widget.Holo.Button.Borderless"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginLeft="1px"
android:layout_marginRight="1px"
android:layout_weight="3" android:layout_weight="3"
android:background="@drawable/button_custom"
android:clickable="true" android:clickable="true"
android:focusable="false" android:focusable="false"
android:longClickable="true" android:longClickable="true"
android:text="⚙" android:text="⚙"
android:textColor="@color/button_text"
android:textSize="24sp"/> android:textSize="24sp"/>
<View
android:id="@+id/main_separator_left"
style="@style/hSeparator"
android:background="@drawable/button_separator_dark" />
<Button <Button
android:id="@+id/main_mid" android:id="@+id/main_mid"
style="@android:style/Widget.Holo.Button.Borderless"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginLeft="1px"
android:layout_marginRight="1px"
android:layout_weight="5" android:layout_weight="5"
android:background="@drawable/button_custom"
android:clickable="true" android:clickable="true"
android:focusable="false" android:focusable="false"
android:longClickable="true" android:longClickable="true"
android:paddingLeft="2dp" android:text="@android:string/ok" />
android:paddingRight="2dp"
android:text="@android:string/ok"
android:textColor="@color/button_text" />
<View
android:id="@+id/main_separator_right"
android:background="@drawable/button_separator_dark"
style="@style/hSeparator" />
<Button <Button
android:id="@+id/main_right" android:id="@+id/main_right"
style="@android:style/Widget.Holo.Button.Borderless"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginLeft="1px"
android:layout_marginRight="1px"
android:layout_weight="3" android:layout_weight="3"
android:background="@drawable/button_custom"
android:clickable="true" android:clickable="true"
android:focusable="false" android:focusable="false"
android:longClickable="true" android:longClickable="true"
android:text="⌫" android:text="⌫"
android:textColor="@color/button_text"
android:textSize="24sp" /> android:textSize="24sp" />
</LinearLayout> </LinearLayout>

View file

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- taken from Sharp 007SH -->
<ImageView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_gravity="center_vertical"
android:background="@drawable/btn_circle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="4.0dip"
android:src="@drawable/ic_btn_round_more"/>

View file

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/* //device/apps/common/assets/res/layout/list_content.xml
**
** Copyright 2006, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
-->
<ListView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:drawSelectorOnTop="false"
android:scrollbarAlwaysDrawVerticalTrack="true"
/>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/preferences_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:context=".preferences.PreferencesFragment">
</FrameLayout>

View file

@ -1,29 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modfied from android preference.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
android:gravity="center_vertical"
android:paddingRight="?android:attr/scrollbarSize"
android:baselineAligned="false">
<RelativeLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="15dip"
android:layout_marginRight="6dip"
android:layout_marginTop="6dip"
android:layout_marginBottom="6dip"
android:layout_weight="1">
<TextView android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceLarge"
android:ellipsize="marquee"
android:fadingEdge="horizontal" />
</RelativeLayout>
</LinearLayout>

View file

@ -1,37 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modfied from android preference.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
android:gravity="center_vertical"
android:paddingRight="?android:attr/scrollbarSize"
android:baselineAligned="false">
<RelativeLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="15dip"
android:layout_marginRight="6dip"
android:layout_marginTop="6dip"
android:layout_marginBottom="6dip"
android:layout_weight="1">
<TextView android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceLarge"
android:ellipsize="marquee"
android:fadingEdge="horizontal" />
<TextView android:id="@+id/summary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/title"
android:layout_alignLeft="@+id/title"
android:textAppearance="?android:attr/textAppearanceSmall"
android:maxLines="4" />
</RelativeLayout>
</LinearLayout>

View file

@ -1,43 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modfied from android preference.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
android:gravity="center_vertical"
android:paddingRight="?android:attr/scrollbarSize"
android:baselineAligned="false">
<RelativeLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="15dip"
android:layout_marginRight="6dip"
android:layout_marginTop="6dip"
android:layout_marginBottom="6dip"
android:layout_weight="1">
<TextView android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceLarge"
android:ellipsize="marquee"
android:fadingEdge="horizontal" />
<TextView android:id="@+id/summary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/title"
android:layout_alignLeft="@+id/title"
android:textAppearance="?android:attr/textAppearanceSmall"
android:maxLines="4" />
</RelativeLayout>
<!-- Preference should place its actual preference widget here. -->
<LinearLayout android:id="@+id/widget_frame"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="vertical" />
</LinearLayout>

View file

@ -12,6 +12,5 @@
android:paddingHorizontal="@dimen/candidate_padding_horizontal" android:paddingHorizontal="@dimen/candidate_padding_horizontal"
android:paddingVertical="@dimen/candidate_padding_vertical" android:paddingVertical="@dimen/candidate_padding_vertical"
android:text="" android:text=""
android:textColor="@color/candidate_color"
android:textSize="@dimen/candidate_font_size" /> android:textSize="@dimen/candidate_font_size" />
</LinearLayout> </LinearLayout>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"> <resources>
<string name="app_settings">Настройки на Traditional T9</string> <string name="app_settings">Настройки на Traditional T9</string>
<string name="close">Затвори</string> <string name="close">Затвори</string>
<string name="completed">Завършено</string> <string name="completed">Завършено</string>
@ -12,21 +12,34 @@
<string name="add_word_invalid_language">За да добавите нова дума, първо изберете език.</string> <string name="add_word_invalid_language">За да добавите нова дума, първо изберете език.</string>
<string name="add_word_title">Добавяне на дума</string> <string name="add_word_title">Добавяне на дума</string>
<string name="pref_category_about">За приложението</string>
<string name="pref_help">Помощ</string> <string name="pref_help">Помощ</string>
<string name="pref_dark_theme">Тъмен облик</string>
<string name="pref_choose_languages">Избор на езици</string> <string name="pref_choose_languages">Избор на езици</string>
<string name="pref_loaddict">Зареди речник</string> <string name="dictionary_truncate_title">Изтрий речник</string>
<string name="pref_loaduserdict">Зареди свой речник</string>
<string name="pref_truncatedict">Изтрий речник</string>
<string name="pref_category_dictionaries">Речници</string>
<string name="dictionary_cancel_load">Отмени зареждането</string>
<string name="dictionary_load_bad_char">Неуспешно зареждане. Невалидна дума \"%1$s\" на ред %2$d за език \"%3$s\".</string> <string name="dictionary_load_bad_char">Неуспешно зареждане. Невалидна дума \"%1$s\" на ред %2$d за език \"%3$s\".</string>
<string name="dictionary_load_cancelled">Зареждането на речник е отменено.</string>
<string name="dictionary_load_error">Несупешно зареждане на речник за език \"%1$s\" (%2$s).</string> <string name="dictionary_load_error">Несупешно зареждане на речник за език \"%1$s\" (%2$s).</string>
<string name="dictionary_load_failed">Неуспешно зареждане на речник.</string> <string name="dictionary_load_failed">Неуспешно зареждане на речник.</string>
<string name="dictionary_loaded">Зареждането на речник приключи.</string> <string name="dictionary_loaded">Зареждането на речник приключи.</string>
<string name="dictionary_loading">Зареждане на речник (%1$s)…</string> <string name="dictionary_loading">Зареждане на речник (%1$s)…</string>
<string name="dictionary_loading_user_dict">Зареждане на вашия речник…</string> <string name="dictionary_load_title">Зареди речник</string>
<string name="dictionary_load_title">Зареждане на речник</string>
<string name="dictionary_not_found">Неуспешно зареждане. Липсва речник за \"%1$s\".</string> <string name="dictionary_not_found">Неуспешно зареждане. Липсва речник за \"%1$s\".</string>
<string name="dictionary_truncated">Речникът е изтрит успешно.</string> <string name="dictionary_truncated">Речникът е изтрит успешно.</string>
<string name="pref_category_appearance">Облик</string>
<string name="pref_category_function_keys">Бутони за бърз достъп</string>
<string name="pref_show_soft_function_keys">Бутони на екрана</string>
<string name="key_back">Назад</string>
<string name="key_call">Зелена слушалка</string>
<string name="function_add_word_key">Добавяне на нова дума</string>
<string name="function_backspace_key">Триене на текст</string>
<string name="function_next_language_key">Следващ eзик</string>
<string name="function_next_mode_key">Режим на писане</string>
<string name="function_show_settings_key">Настройки</string>
<string name="function_reset_keys_title">Възстанови стандартните бутони</string>
<string name="function_reset_keys_done">Възстановени са стандартните \"бързи\" бутони.</string>
<string name="key_hold_key">(задръж)</string>
</resources> </resources>

View file

@ -10,14 +10,14 @@
<string name="add_word_exist">Das Wort \"%1$s\" ist bereits in Wörterbuch.</string> <string name="add_word_exist">Das Wort \"%1$s\" ist bereits in Wörterbuch.</string>
<string name="add_word_title">Wort hinzufügen</string> <string name="add_word_title">Wort hinzufügen</string>
<string name="pref_help">"Hilfe anzeigen</string> <string name="pref_category_about">Über die Anwendung</string>
<string name="pref_choose_languages">Sprachen</string> <string name="pref_help">Hilfe</string>
<string name="pref_loaddict">Wörterbuch laden</string> <string name="pref_dark_theme">Dunkles Thema</string>
<string name="pref_loaduserdict">Benutzerwörterbuch laden</string> <string name="pref_choose_languages">Sprachen auswählen</string>
<string name="pref_truncatedict">Wörterbuch löschen</string> <string name="dictionary_truncate_title">Wörterbuch löschen</string>
<string name="pref_category_dictionaries">Wörterbücher</string>
<string name="dictionary_loading">Lade Wörterbuch (%1$s)…</string> <string name="dictionary_loading">Lade Wörterbuch (%1$s)…</string>
<string name="dictionary_loading_user_dict">Lade Benutzerwörterbuch…</string>
<string name="dictionary_load_title">Wörterbuch laden</string> <string name="dictionary_load_title">Wörterbuch laden</string>
<string name="dictionary_not_found">Wird nicht geladen. Wörterbuch für \"%1$s\" nicht gefunden.</string> <string name="dictionary_not_found">Wird nicht geladen. Wörterbuch für \"%1$s\" nicht gefunden.</string>
</resources> </resources>

View file

@ -11,18 +11,20 @@
<string name="add_word_exist">Le mot «%1$s» est déjà dans le dictionnaire.</string> <string name="add_word_exist">Le mot «%1$s» est déjà dans le dictionnaire.</string>
<string name="add_word_title">Ajouter un mot</string> <string name="add_word_title">Ajouter un mot</string>
<string name="pref_help">"Afficher l\'aide</string>
<string name="pref_choose_languages">Choisir langues</string>
<string name="pref_loaddict">Charger le dictionnaire</string>
<string name="pref_loaduserdict">Charger le dictionnaire utilisateur</string>
<string name="pref_truncatedict">Supprimer le dictionaire</string>
<string name="pref_category_about">À propos de l\'application</string>
<string name="pref_help">Aide</string>
<string name="pref_dark_theme">Thème sombre</string>
<string name="pref_choose_languages">Choisir langues</string>
<string name="dictionary_truncate_title">Supprimer le dictionaire</string>
<string name="pref_category_dictionaries">Dictionnaires</string>
<string name="dictionary_cancel_load">Annuler le chargement</string>
<string name="dictionary_load_error">Echec du chargement de dictionnaire pour langue «%1$s» (%2$s).</string> <string name="dictionary_load_error">Echec du chargement de dictionnaire pour langue «%1$s» (%2$s).</string>
<string name="dictionary_load_cancelled">Chargement du dictionnaire annulée.</string>
<string name="dictionary_load_failed">Echec du chargement de dictionnaire.</string> <string name="dictionary_load_failed">Echec du chargement de dictionnaire.</string>
<string name="dictionary_loaded">Chargement du dictionnaire terminé.</string> <string name="dictionary_loaded">Chargement du dictionnaire terminé.</string>
<string name="dictionary_loading">Chargement du dictionnaire (%1$s)…</string> <string name="dictionary_loading">Chargement du dictionnaire (%1$s)…</string>
<string name="dictionary_loading_user_dict">Chargement du dictionnaire utilisateur…</string>
<string name="dictionary_load_title">Charger le dictionnaire</string> <string name="dictionary_load_title">Charger le dictionnaire</string>
<string name="dictionary_not_found">Echec du chargement. Dictionnaire «%1$s» introuvable.</string> <string name="dictionary_not_found">Echec du chargement. Dictionnaire «%1$s» introuvable.</string>
<string name="pref_category_function_keys">Raccourcis clavier</string>
</resources> </resources>

View file

@ -11,18 +11,19 @@
<string name="add_word_exist">Parola “%1$s” già nel dizionario.</string> <string name="add_word_exist">Parola “%1$s” già nel dizionario.</string>
<string name="add_word_title">Aggiungi parola</string> <string name="add_word_title">Aggiungi parola</string>
<string name="pref_help">"Mostra aiuto</string> <string name="pref_category_about">Sull\'applicazione</string>
<string name="pref_choose_languages">Le lingue</string> <string name="pref_help">Aiuto</string>
<string name="pref_loaddict">Carica dizionario</string> <string name="pref_dark_theme">Tema scuro</string>
<string name="pref_loaduserdict">Carica dizionario utente</string> <string name="pref_choose_languages">Scegli le lingue</string>
<string name="pref_truncatedict">Eliminare il dizionario</string> <string name="dictionary_truncate_title">Eliminare il dizionario</string>
<string name="dictionary_load_cancelled">Caricamento del dizionario annullato.</string> <string name="pref_category_dictionaries">Dizionari</string>
<string name="dictionary_cancel_load">Annullare il caricamento</string>
<string name="dictionary_load_failed">Caricamento del dizionario non riuscito.</string> <string name="dictionary_load_failed">Caricamento del dizionario non riuscito.</string>
<string name="dictionary_loaded">Caricamento del dizionario terminato.</string> <string name="dictionary_loaded">Caricamento del dizionario terminato.</string>
<string name="dictionary_loading">Caricamento del dizionario (%1$s)…</string> <string name="dictionary_loading">Caricamento del dizionario (%1$s)…</string>
<string name="dictionary_loading_user_dict">Caricamento dizionario utente…</string> <string name="dictionary_load_title">Carica il dizionario</string>
<string name="dictionary_load_title">Caricamento del dizionario</string>
<string name="dictionary_not_found">Impossibile caricare. Dizionario per “%1$s” non trovato.</string> <string name="dictionary_not_found">Impossibile caricare. Dizionario per “%1$s” non trovato.</string>
<string name="pref_category_function_keys">Scorciatoie da tastiera</string>
</resources> </resources>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"> <resources>
<string name="app_settings">Traditional T9 Opties</string> <string name="app_settings">Traditional T9 Opties</string>
<string name="close">Sluiten</string> <string name="close">Sluiten</string>
<string name="error_unexpected">Er is een onverwachte fout opgetreden.</string> <string name="error_unexpected">Er is een onverwachte fout opgetreden.</string>
@ -10,13 +10,13 @@
<string name="add_word_exist">Woord \"%1$s\" staat al in het woordenboek.</string> <string name="add_word_exist">Woord \"%1$s\" staat al in het woordenboek.</string>
<string name="add_word_invalid_language">Kan geen woord invoegen als er geen taal is geselecteerd.</string> <string name="add_word_invalid_language">Kan geen woord invoegen als er geen taal is geselecteerd.</string>
<string name="add_word_title">Woord toevoegen</string> <string name="add_word_title">Woord toevoegen</string>
<string name="pref_help">Laat help zien</string> <string name="pref_category_about">Over de applicatie</string>
<string name="pref_help">Helpen</string>
<string name="pref_dark_theme">Donker thema</string>
<string name="pref_choose_languages">Talen kiezen</string> <string name="pref_choose_languages">Talen kiezen</string>
<string name="pref_loaddict">Woordenboek laden</string> <string name="dictionary_truncate_title">Woordenboek wissen</string>
<string name="pref_loaduserdict">Gebruikerswoordenboek laden</string> <string name="pref_category_dictionaries">Woordenboeken</string>
<string name="pref_truncatedict">Woordenboek wissen</string>
<string name="dictionary_loading">Woordenboek laden (%1$s)…</string> <string name="dictionary_loading">Woordenboek laden (%1$s)…</string>
<string name="dictionary_loading_user_dict">Gebruikerswoordenboek laden…</string>
<string name="dictionary_load_title">Woordenboek laden</string> <string name="dictionary_load_title">Woordenboek laden</string>
<string name="dictionary_not_found">Laden mislukt. Woordenboek voor %1$s niet gevonden.</string> <string name="dictionary_not_found">Laden mislukt. Woordenboek voor %1$s niet gevonden.</string>
<string name="dictionary_truncated">Woordenboek succesvol gewist.</string> <string name="dictionary_truncated">Woordenboek succesvol gewist.</string>

View file

@ -12,18 +12,18 @@
<string name="add_word_invalid_language">Чтобы добавить новое слово, сначала выберите язык.</string> <string name="add_word_invalid_language">Чтобы добавить новое слово, сначала выберите язык.</string>
<string name="add_word_title">Добавить слово</string> <string name="add_word_title">Добавить слово</string>
<string name="pref_help">О программе</string> <string name="pref_category_about">О приложении</string>
<string name="pref_help">Помощь</string>
<string name="pref_dark_theme">Темная тема</string>
<string name="pref_choose_languages">Выбор языков</string> <string name="pref_choose_languages">Выбор языков</string>
<string name="pref_loaddict">Загрузить словарь</string> <string name="dictionary_truncate_title">Очистить словарь</string>
<string name="pref_loaduserdict">Загрузить свой словарь</string>
<string name="pref_truncatedict">Очистить словарь</string>
<string name="dictionary_load_cancelled">Загрузка словаря отменена.</string> <string name="pref_category_dictionaries">Словари</string>
<string name="dictionary_cancel_load">Отменить загрузку</string>
<string name="dictionary_load_error">Ошибка загрузки словаря для языка «%1$s» (%2$s).</string> <string name="dictionary_load_error">Ошибка загрузки словаря для языка «%1$s» (%2$s).</string>
<string name="dictionary_load_failed">Ошибка загрузки словаря.</string> <string name="dictionary_load_failed">Ошибка загрузки словаря.</string>
<string name="dictionary_loaded">Загрузка словаря завершена.</string> <string name="dictionary_loaded">Загрузка словаря завершена.</string>
<string name="dictionary_loading">Загрузка словаря (%1$s)…</string> <string name="dictionary_loading">Загрузка словаря (%1$s)…</string>
<string name="dictionary_loading_user_dict">Загрузка пользовательского словаря…</string>
<string name="dictionary_load_title">Загрузить словарь</string> <string name="dictionary_load_title">Загрузить словарь</string>
<string name="dictionary_not_found">Ошибка загрузки. Словарь «%1$s» не найден.</string> <string name="dictionary_not_found">Ошибка загрузки. Словарь «%1$s» не найден.</string>
<string name="dictionary_truncated">Словарь успешно очищен.</string> <string name="dictionary_truncated">Словарь успешно очищен.</string>

View file

@ -11,18 +11,18 @@
<string name="add_word_exist">Слово «%1$s» вже є в словнику.</string> <string name="add_word_exist">Слово «%1$s» вже є в словнику.</string>
<string name="add_word_title">Додати слово</string> <string name="add_word_title">Додати слово</string>
<string name="pref_help">Про програму</string> <string name="pref_category_about">Про додаток</string>
<string name="pref_help">Допомога</string>
<string name="pref_dark_theme">Темна тема</string>
<string name="pref_choose_languages">Вибір мови</string> <string name="pref_choose_languages">Вибір мови</string>
<string name="pref_loaddict">Завантажити словник</string> <string name="dictionary_truncate_title">Очистити словник</string>
<string name="pref_loaduserdict">Завантажити свій словник</string>
<string name="pref_truncatedict">Очистити словник</string>
<string name="dictionary_load_cancelled">Завантаження словника скасовано.</string> <string name="pref_category_dictionaries">Словники</string>
<string name="dictionary_cancel_load">Скасувати завантаження</string>
<string name="dictionary_load_error">Помилка завантаження словника для мови «%1$s» (%2$s).</string> <string name="dictionary_load_error">Помилка завантаження словника для мови «%1$s» (%2$s).</string>
<string name="dictionary_load_failed">Помилка завантаження словника.</string> <string name="dictionary_load_failed">Помилка завантаження словника.</string>
<string name="dictionary_loaded">Завантаження словника завершено.</string> <string name="dictionary_loaded">Завантаження словника завершено.</string>
<string name="dictionary_loading">Завантаження словника (%1$s)…</string> <string name="dictionary_loading">Завантаження словника (%1$s)…</string>
<string name="dictionary_loading_user_dict">Завантаження словника користувача…</string>
<string name="dictionary_load_title">Завантажити словник</string> <string name="dictionary_load_title">Завантажити словник</string>
<string name="dictionary_not_found">Помилка завантаження. Словник «%1$s» не знайдено.</string> <string name="dictionary_not_found">Помилка завантаження. Словник «%1$s» не знайдено.</string>
</resources> </resources>

View file

@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="pref_lang_titles">
<item>English</item>
<item>Русский</item>
<item>Deutsch</item>
<item>Français</item>
<item>Italiano</item>
<item>Українська</item>
<item>Български</item>
<item>Nederlands</item>
</string-array>
<!--pref_lang_titles pair with pref_lang_values -->
<integer-array translatable="false" name="pref_lang_values">
<item>@integer/LANG_EN</item>
<item>@integer/LANG_RU</item>
<item>@integer/LANG_DE</item>
<item>@integer/LANG_FR</item>
<item>@integer/LANG_IT</item>
<item>@integer/LANG_UK</item>
<item>@integer/LANG_BG</item>
<item>@integer/LANG_NL</item>
</integer-array>
</resources>

View file

@ -1,8 +1,17 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<color name="button_text">#EFEBE9</color> <!-- Light theme -->
<color name="candidate_background">#333333</color> <color name="button_text">#242424</color>
<color name="candidate_color">#CCCCCC</color>
<color name="candidate_selected">#555555</color> <color name="candidate_background">#CECECE</color>
<color name="candidate_color">#202020</color>
<color name="candidate_selected">#AAAAAA</color>
<color name="candidate_separator">#888888</color> <color name="candidate_separator">#888888</color>
<!-- Dark theme -->
<color name="dark_button_text">#C0C0C0</color>
<color name="dark_candidate_background">#333333</color>
<color name="dark_candidate_color">#CCCCCC</color>
<color name="dark_candidate_selected">#555555</color>
</resources> </resources>

View file

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- MAKE SURE THESE MATCH WITH with IDs in "languages/definitions" -->
<integer name="LANG_EN">1</integer>
<integer name="LANG_RU">2</integer>
<integer name="LANG_DE">3</integer>
<integer name="LANG_FR">4</integer>
<integer name="LANG_IT">5</integer>
<integer name="LANG_UK">6</integer>
<integer name="LANG_BG">7</integer>
<integer name="LANG_NL">8</integer>
<integer name="LANG_DEFAULT">@integer/LANG_EN</integer>
</resources>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"> <resources>
<string translatable="false" name="help_url">https://github.com/sspanak/tt9/blob/master/docs/user-manual.md</string> <string translatable="false" name="help_url">https://github.com/sspanak/tt9/blob/master/docs/user-manual.md</string>
<string name="app_name" translatable="false">Traditional T9</string> <string name="app_name" translatable="false">Traditional T9</string>
<string name="app_settings">Traditional T9 Settings</string> <string name="app_settings">Traditional T9 Settings</string>
@ -14,21 +14,45 @@
<string name="add_word_invalid_language">Cannot insert a word when no language is selected.</string> <string name="add_word_invalid_language">Cannot insert a word when no language is selected.</string>
<string name="add_word_title">Add Word</string> <string name="add_word_title">Add Word</string>
<string name="pref_help">Show Help</string> <string name="pref_category_about">About</string>
<string name="pref_choose_languages">Choose Languages</string> <string name="pref_category_appearance">Appearance</string>
<string name="pref_loaddict">Load dictionary</string> <string name="pref_category_dictionaries">Dictionaries</string>
<string name="pref_loaduserdict">Load user dictionary</string> <string name="pref_category_function_keys">Select Hotkeys</string>
<string translatable="false" name="pref_loaduserdictdesc">SDcard/traditionalt9/user.lang.dict (lang: en/ru/de/fr)</string>
<string name="pref_truncatedict">Clear dictionary</string>
<string name="pref_choose_languages">Choose Languages</string>
<string name="pref_dark_theme">Dark Theme</string>
<string name="pref_show_soft_function_keys">Show on-screen keys</string>
<string name="pref_help">Help</string>
<string name="dictionary_cancel_load">Cancel loading</string>
<string name="dictionary_load_bad_char">Loading failed. Invalid word \"%1$s\" on line %2$d of language \"%3$s\".</string> <string name="dictionary_load_bad_char">Loading failed. Invalid word \"%1$s\" on line %2$d of language \"%3$s\".</string>
<string name="dictionary_load_cancelled">Dictionary load cancelled.</string>
<string name="dictionary_load_error">Failed loading the dictionary for language \"%1$s\" (%2$s).</string> <string name="dictionary_load_error">Failed loading the dictionary for language \"%1$s\" (%2$s).</string>
<string name="dictionary_load_failed">Dictionary load failed.</string> <string name="dictionary_load_failed">Dictionary load failed.</string>
<string name="dictionary_loaded">Dictionary load completed.</string> <string name="dictionary_loaded">Dictionary load completed.</string>
<string name="dictionary_loading">Loading dictionary (%1$s)…</string> <string name="dictionary_loading">Loading dictionary (%1$s)…</string>
<string name="dictionary_loading_user_dict">Loading user dictionary…</string>
<string name="dictionary_load_title">Load dictionary</string> <string name="dictionary_load_title">Load dictionary</string>
<string name="dictionary_not_found">Loading failed. Dictionary for \"%1$s\" not found.</string> <string name="dictionary_not_found">Loading failed. Dictionary for \"%1$s\" not found.</string>
<string name="dictionary_truncate_title">Clear dictionary</string>
<string name="dictionary_truncated">Dictionary successfully cleared.</string> <string name="dictionary_truncated">Dictionary successfully cleared.</string>
<string name="function_add_word_key">Add Word key</string>
<string name="function_backspace_key">Backspace key</string>
<string name="function_next_language_key">Next Language key</string>
<string name="function_next_mode_key">Input Mode key</string>
<string name="function_show_settings_key">Show Settings key</string>
<string name="function_reset_keys_title">Restore Default Keys</string>
<string name="function_reset_keys_done">Default key settings restored.</string>
<string name="key_hold_key">(hold)</string>
<string name="key_none" translatable="false">--</string>
<string name="key_back">Back</string>
<string name="key_call">Call</string>
<string name="key_delete" translatable="false">Delete</string>
<string name="key_f1" translatable="false">F1</string>
<string name="key_f2" translatable="false">F2</string>
<string name="key_f3" translatable="false">F3</string>
<string name="key_f4" translatable="false">F4</string>
<string name="key_menu" translatable="false">Menu</string>
<string name="key_pound" translatable="false">#</string>
<string name="key_star" translatable="false"></string>
</resources> </resources>

View file

@ -1,24 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<style name="hSeparator">
<!-- <item name="android:layout_height">match_parent</item>
Base application theme, dependent on API level. This theme is replaced <item name="android:layout_width">2px</item>
by AppBaseTheme from res/values-vXX/styles.xml on newer devices. <item name="android:layout_marginBottom">1dp</item>
--> <item name="android:layout_marginTop">1dp</item>
<style name="AppBaseTheme" parent="android:Theme.Light">
<!--
Theme customizations available in newer API levels can go in
res/values-vXX/styles.xml, while customizations related to
backward-compatibility can go here.
-->
</style> </style>
<!-- Application theme. -->
<style name="AppTheme" parent="AppBaseTheme">
<!-- All customizations that are NOT specific to a particular API-level can go here. -->
</style>
<declare-styleable name="PrefItem">
<attr name="title" format="string" />
<attr name="summary" format="string" />
</declare-styleable>
</resources> </resources>

View file

@ -22,5 +22,5 @@
<input-method <input-method
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:settingsActivity="io.github.sspanak.tt9.ui.TraditionalT9Settings"> android:settingsActivity="io.github.sspanak.tt9.preferences.PreferencesActivity">
</input-method> </input-method>

View file

@ -1,14 +1,109 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Settings> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
<Setting title="@string/pref_help" id="help" /> xmlns:app="http://schemas.android.com/apk/res-auto"
<SettingMultiList app:orderingFromXml="true">
id="pref_lang_support"
defaultValue="@integer/LANG_DEFAULT"
entries="@array/pref_lang_titles" <Preference
entryValues="@array/pref_lang_values" app:iconSpaceReserved="false"
title="@string/pref_choose_languages" /> app:key="help"
<Setting title="@string/pref_loaddict" id="loaddict"/> app:summary="github.com/sspanak/tt9"
<Setting title="@string/pref_truncatedict" id="truncatedict"/> app:title="@string/pref_help">
<!-- Take care of dictionary loading in #26 --> <intent
<!-- <Setting title="@string/pref_loaduserdict" summary="@string/pref_loaduserdictdesc" id="loaduserdict"/> --> android:action="android.intent.action.VIEW"
</Settings> android:data="@string/help_url" />
</Preference>
<PreferenceCategory
android:title="@string/pref_category_appearance"
app:iconSpaceReserved="false"
app:singleLineTitle="true">
<SwitchPreferenceCompat
app:defaultValue="true"
app:iconSpaceReserved="false"
app:key="pref_dark_theme"
app:title="@string/pref_dark_theme" />
<SwitchPreferenceCompat
app:defaultValue="true"
app:iconSpaceReserved="false"
app:key="pref_show_soft_keys"
app:title="@string/pref_show_soft_function_keys" />
</PreferenceCategory>
<PreferenceCategory
android:title="@string/pref_category_dictionaries"
app:iconSpaceReserved="false"
app:singleLineTitle="true">
<MultiSelectListPreference
app:iconSpaceReserved="false"
app:key="pref_languages"
app:title="@string/pref_choose_languages" />
<Preference
app:iconSpaceReserved="false"
app:key="dictionary_load"
app:title="@string/dictionary_load_title" />
<Preference
app:iconSpaceReserved="false"
app:key="dictionary_truncate"
app:title="@string/dictionary_truncate_title" />
</PreferenceCategory>
<PreferenceCategory
android:title="@string/pref_category_function_keys"
app:iconSpaceReserved="false"
app:singleLineTitle="true">
<DropDownPreference
app:iconSpaceReserved="false"
app:key="key_add_word"
app:title="@string/function_add_word_key" />
<DropDownPreference
app:iconSpaceReserved="false"
app:key="key_backspace"
app:title="@string/function_backspace_key" />
<DropDownPreference
app:iconSpaceReserved="false"
app:key="key_next_language"
app:title="@string/function_next_language_key" />
<DropDownPreference
app:iconSpaceReserved="false"
app:key="key_next_input_mode"
app:title="@string/function_next_mode_key" />
<DropDownPreference
app:iconSpaceReserved="false"
app:key="key_show_settings"
app:title="@string/function_show_settings_key" />
<Preference
app:iconSpaceReserved="false"
app:key="reset_keys"
app:title="@string/function_reset_keys_title" />
</PreferenceCategory>
<PreferenceCategory
android:title="@string/pref_category_about"
app:iconSpaceReserved="false"
app:singleLineTitle="true">
<Preference
app:iconSpaceReserved="false"
app:key="version_info"
app:title="@string/app_name" />
</PreferenceCategory>
</PreferenceScreen>

View file

@ -17,11 +17,13 @@ import io.github.sspanak.tt9.Logger;
import io.github.sspanak.tt9.languages.InvalidLanguageCharactersException; import io.github.sspanak.tt9.languages.InvalidLanguageCharactersException;
import io.github.sspanak.tt9.languages.InvalidLanguageException; import io.github.sspanak.tt9.languages.InvalidLanguageException;
import io.github.sspanak.tt9.languages.Language; import io.github.sspanak.tt9.languages.Language;
import io.github.sspanak.tt9.preferences.T9Preferences; import io.github.sspanak.tt9.preferences.SettingsStore;
public class DictionaryLoader { public class DictionaryLoader {
private static DictionaryLoader self;
private final AssetManager assets; private final AssetManager assets;
private final T9Preferences prefs; private final SettingsStore settings;
private final Pattern containsPunctuation = Pattern.compile("\\p{Punct}(?<!-)"); private final Pattern containsPunctuation = Pattern.compile("\\p{Punct}(?<!-)");
private Thread loadThread; private Thread loadThread;
@ -30,9 +32,20 @@ public class DictionaryLoader {
private long lastProgressUpdate = 0; private long lastProgressUpdate = 0;
public static DictionaryLoader getInstance(Context context) {
if (self == null) {
self = new DictionaryLoader(context);
}
return self;
}
public DictionaryLoader(Context context) { public DictionaryLoader(Context context) {
assets = context.getAssets(); assets = context.getAssets();
prefs = T9Preferences.getInstance(); settings = new SettingsStore(context);
} }
@ -180,14 +193,14 @@ public class DictionaryLoader {
validateWord(language, word, line); validateWord(language, word, line);
dbWords.add(stringToWord(language, word)); dbWords.add(stringToWord(language, word));
if (line % prefs.getDictionaryImportWordChunkSize() == 0) { if (line % settings.getDictionaryImportWordChunkSize() == 0) {
DictionaryDb.insertWordsSync(dbWords); DictionaryDb.insertWordsSync(dbWords);
dbWords.clear(); dbWords.clear();
} }
if (totalWords > 0) { if (totalWords > 0) {
int progress = (int) Math.floor(100.0 * line / totalWords); int progress = (int) Math.floor(100.0 * line / totalWords);
sendProgressMessage(handler, language, progress, prefs.getDictionaryImportProgressUpdateInterval()); sendProgressMessage(handler, language, progress, settings.getDictionaryImportProgressUpdateInterval());
} }
} }

View file

@ -71,7 +71,7 @@ class InputFieldHelper {
* determineInputModes * determineInputModes
* Determine the typing mode based on the input field being edited. Returns an ArrayList of the allowed modes. * Determine the typing mode based on the input field being edited. Returns an ArrayList of the allowed modes.
* *
* @return ArrayList<T9Preferences.MODE_ABC | T9Preferences.MODE_123 | T9Preferences.MODE_PREDICTIVE> * @return ArrayList<SettingsStore.MODE_ABC | SettingsStore.MODE_123 | SettingsStore.MODE_PREDICTIVE>
*/ */
public static ArrayList<Integer> determineInputModes(EditorInfo inputField) { public static ArrayList<Integer> determineInputModes(EditorInfo inputField) {
final int INPUT_TYPE_SHARP_007H_PHONE_BOOK = 65633; final int INPUT_TYPE_SHARP_007H_PHONE_BOOK = 65633;
@ -134,7 +134,7 @@ class InputFieldHelper {
*/ */
public static void determineTextCase(EditorInfo inputField) { public static void determineTextCase(EditorInfo inputField) {
// Logger.d("updateShift", "CM start: " + mCapsMode); // Logger.d("updateShift", "CM start: " + mCapsMode);
// if (inputField != null && mCapsMode != T9Preferences.CASE_UPPER) { // if (inputField != null && mCapsMode != SettingsStore.CASE_UPPER) {
// int caps = 0; // int caps = 0;
// if (inputField.inputType != InputType.TYPE_NULL) { // if (inputField.inputType != InputType.TYPE_NULL) {
// caps = currentInputConnection.getCursorCapsMode(inputField.inputType); // caps = currentInputConnection.getCursorCapsMode(inputField.inputType);
@ -142,13 +142,13 @@ class InputFieldHelper {
// // mInputView.setShifted(mCapsLock || caps != 0); // // mInputView.setShifted(mCapsLock || caps != 0);
// // Logger.d("updateShift", "caps: " + caps); // // Logger.d("updateShift", "caps: " + caps);
// if ((caps & TextUtils.CAP_MODE_CHARACTERS) == TextUtils.CAP_MODE_CHARACTERS) { // 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) { // } 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) { // } else if ((caps & TextUtils.CAP_MODE_WORDS) == TextUtils.CAP_MODE_WORDS) {
// mCapsMode = T9Preferences.CASE_CAPITALIZE; // mCapsMode = SettingsStore.CASE_CAPITALIZE;
// } else { // } else {
// mCapsMode = T9Preferences.CASE_LOWER; // mCapsMode = SettingsStore.CASE_LOWER;
// } // }
// updateStatusIcon(); // updateStatusIcon();
// } // }

View file

@ -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.Language;
import io.github.sspanak.tt9.languages.LanguageCollection; import io.github.sspanak.tt9.languages.LanguageCollection;
import io.github.sspanak.tt9.languages.definitions.English; 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 class InputModeValidator {
public static ArrayList<Integer> validateEnabledLanguages(T9Preferences prefs, ArrayList<Integer> enabledLanguageIds) { public static ArrayList<Integer> validateEnabledLanguages(SettingsStore settings, ArrayList<Integer> enabledLanguageIds) {
ArrayList<Language> validLanguages = LanguageCollection.getAll(enabledLanguageIds); ArrayList<Language> validLanguages = LanguageCollection.getAll(enabledLanguageIds);
ArrayList<Integer> validLanguageIds = new ArrayList<>(); ArrayList<Integer> validLanguageIds = new ArrayList<>();
for (Language lang : validLanguages) { 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."); Logger.e("tt9/validateEnabledLanguages", "The language list seems to be corrupted. Resetting to first language only.");
} }
prefs.saveEnabledLanguages(validLanguageIds); settings.saveEnabledLanguageIds(validLanguageIds);
return validLanguageIds; return validLanguageIds;
} }
public static Language validateLanguage(T9Preferences prefs, Language language, ArrayList<Integer> validLanguageIds) { public static Language validateLanguage(SettingsStore settings, Language language, ArrayList<Integer> validLanguageIds) {
if (language != null && validLanguageIds.contains(language.getId())) { if (language != null && validLanguageIds.contains(language.getId())) {
return language; return language;
} }
@ -36,20 +36,20 @@ public class InputModeValidator {
Language validLanguage = LanguageCollection.getLanguage(validLanguageIds.get(0)); Language validLanguage = LanguageCollection.getLanguage(validLanguageIds.get(0));
validLanguage = validLanguage == null ? LanguageCollection.getLanguage(1) : validLanguage; validLanguage = validLanguage == null ? LanguageCollection.getLanguage(1) : validLanguage;
validLanguage = validLanguage == null ? new English() : validLanguage; validLanguage = validLanguage == null ? new English() : validLanguage;
prefs.saveInputLanguage(validLanguage.getId()); settings.saveInputLanguage(validLanguage.getId());
Logger.w("tt9/validateSavedLanguage", error + " Enforcing language: " + validLanguage.getId()); Logger.w("tt9/validateSavedLanguage", error + " Enforcing language: " + validLanguage.getId());
return validLanguage; return validLanguage;
} }
public static InputMode validateMode(T9Preferences prefs, InputMode inputMode, ArrayList<Integer> allowedModes) { public static InputMode validateMode(SettingsStore settings, InputMode inputMode, ArrayList<Integer> allowedModes) {
if (allowedModes.size() > 0 && allowedModes.contains(inputMode.getId())) { if (allowedModes.size() > 0 && allowedModes.contains(inputMode.getId())) {
return inputMode; return inputMode;
} }
InputMode newMode = InputMode.getInstance(allowedModes.size() > 0 ? allowedModes.get(0) : InputMode.MODE_123); InputMode newMode = InputMode.getInstance(allowedModes.size() > 0 ? allowedModes.get(0) : InputMode.MODE_123);
prefs.saveInputMode(newMode); settings.saveInputMode(newMode);
if (newMode.getId() != inputMode.getId()) { if (newMode.getId() != inputMode.getId()) {
Logger.w("tt9/validateMode", "Invalid input mode: " + inputMode.getId() + " Enforcing: " + newMode.getId()); Logger.w("tt9/validateMode", "Invalid input mode: " + inputMode.getId() + " Enforcing: " + newMode.getId());
@ -58,12 +58,12 @@ public class InputModeValidator {
return newMode; 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)) { if (!inputMode.setTextCase(newTextCase)) {
inputMode.defaultTextCase(); inputMode.defaultTextCase();
Logger.w("tt9/validateTextCase", "Invalid text case: " + newTextCase + " Enforcing: " + inputMode.getTextCase()); Logger.w("tt9/validateTextCase", "Invalid text case: " + newTextCase + " Enforcing: " + inputMode.getTextCase());
} }
prefs.saveTextCase(inputMode.getTextCase()); settings.saveTextCase(inputMode.getTextCase());
} }
} }

View file

@ -7,13 +7,13 @@ import android.view.View;
import android.view.inputmethod.EditorInfo; import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputConnection;
import io.github.sspanak.tt9.preferences.T9Preferences; import io.github.sspanak.tt9.preferences.SettingsStore;
abstract class KeyPadHandler extends InputMethodService { abstract class KeyPadHandler extends InputMethodService {
protected InputConnection currentInputConnection = null; protected InputConnection currentInputConnection = null;
protected T9Preferences prefs; protected SettingsStore settings;
// editing mode // editing mode
protected static final int NON_EDIT = 0; protected static final int NON_EDIT = 0;
@ -44,7 +44,7 @@ abstract class KeyPadHandler extends InputMethodService {
@Override @Override
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
prefs = new T9Preferences(getApplicationContext()); settings = new SettingsStore(getApplicationContext());
onInit(); onInit();
} }
@ -130,7 +130,7 @@ abstract class KeyPadHandler extends InputMethodService {
@Override @Override
public boolean onKeyDown(int keyCode, KeyEvent event) { public boolean onKeyDown(int keyCode, KeyEvent event) {
if (isOff()) { if (isOff()) {
return super.onKeyDown(keyCode, event); return false;
} }
// Logger.d("onKeyDown", "Key: " + event + " repeat?: " + event.getRepeatCount() + " long-time: " + event.isLongPress()); // 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 // "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, // Also dialer fields seem to handle backspace on their own and we must ignore it,
// otherwise, keyDown race condition occur for all keys. // 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 isThereTextBefore = InputFieldHelper.isThereText(currentInputConnection);
boolean backspaceHandleStatus = handleBackspaceHold(); boolean backspaceHandleStatus = handleBackspaceHold();
@ -170,8 +170,7 @@ abstract class KeyPadHandler extends InputMethodService {
} }
if ( if (
keyCode == prefs.getKeyOtherActions() isSpecialFunctionKey(keyCode)
|| keyCode == prefs.getKeyInputMode()
|| keyCode == KeyEvent.KEYCODE_STAR || keyCode == KeyEvent.KEYCODE_STAR
|| keyCode == KeyEvent.KEYCODE_POUND || keyCode == KeyEvent.KEYCODE_POUND
|| (isNumber(keyCode) && shouldTrackNumPress()) || (isNumber(keyCode) && shouldTrackNumPress())
@ -189,7 +188,7 @@ abstract class KeyPadHandler extends InputMethodService {
@Override @Override
public boolean onKeyLongPress(int keyCode, KeyEvent event) { public boolean onKeyLongPress(int keyCode, KeyEvent event) {
if (isOff()) { if (isOff()) {
return super.onKeyDown(keyCode, event); return false;
} }
// Logger.d("onLongPress", "LONG PRESS: " + keyCode); // Logger.d("onLongPress", "LONG PRESS: " + keyCode);
@ -200,12 +199,8 @@ abstract class KeyPadHandler extends InputMethodService {
ignoreNextKeyUp = keyCode; ignoreNextKeyUp = keyCode;
if (keyCode == prefs.getKeyOtherActions()) { if (handleSpecialFunctionKey(keyCode, true)) {
return onKeyOtherAction(true); return true;
}
if (keyCode == prefs.getKeyInputMode()) {
return onKeyInputMode(true);
} }
switch (keyCode) { switch (keyCode) {
@ -256,7 +251,7 @@ abstract class KeyPadHandler extends InputMethodService {
if ( if (
mEditing != EDITING_DIALER // dialer fields seem to handle backspace on their own mEditing != EDITING_DIALER // dialer fields seem to handle backspace on their own
&& keyCode == prefs.getKeyBackspace() && keyCode == settings.getKeyBackspace()
&& InputFieldHelper.isThereText(currentInputConnection) && InputFieldHelper.isThereText(currentInputConnection)
) { ) {
return true; return true;
@ -277,12 +272,8 @@ abstract class KeyPadHandler extends InputMethodService {
return false; return false;
} }
if (keyCode == prefs.getKeyOtherActions()) { if (handleSpecialFunctionKey(keyCode, false)) {
return onKeyOtherAction(false); return true;
}
if (keyCode == prefs.getKeyInputMode()) {
return onKeyInputMode(false);
} }
switch(keyCode) { 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() { private boolean isOff() {
return currentInputConnection == null || mEditing == NON_EDIT; 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() { protected void resetKeyRepeat() {
isNumKeyRepeated = false; isNumKeyRepeated = false;
lastNumKeyCode = 0; lastNumKeyCode = 0;
@ -381,8 +402,10 @@ abstract class KeyPadHandler extends InputMethodService {
abstract protected boolean onPound(); abstract protected boolean onPound();
// customized key handlers // customized key handlers
abstract protected boolean onKeyInputMode(boolean hold); abstract protected boolean onKeyAddWord();
abstract protected boolean onKeyOtherAction(boolean hold); abstract protected boolean onKeyNextLanguage();
abstract protected boolean onKeyNextInputMode();
abstract protected boolean onKeyShowSettings();
// helpers // helpers
abstract protected void onInit(); abstract protected void onInit();

View file

@ -1,8 +1,13 @@
package io.github.sspanak.tt9.ime; package io.github.sspanak.tt9.ime;
import android.graphics.drawable.Drawable;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; 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.R;
import io.github.sspanak.tt9.ui.UI; import io.github.sspanak.tt9.ui.UI;
@ -12,16 +17,16 @@ class SoftKeyHandler implements View.OnTouchListener {
private final TraditionalT9 tt9; private final TraditionalT9 tt9;
private View view = null; private View view = null;
public SoftKeyHandler(LayoutInflater layoutInflater, TraditionalT9 tt9) { public SoftKeyHandler(TraditionalT9 tt9) {
this.tt9 = tt9; this.tt9 = tt9;
createView(layoutInflater); getView();
} }
View createView(LayoutInflater layoutInflater) { View getView() {
if (view == null) { if (view == null) {
view = layoutInflater.inflate(R.layout.mainview, null); view = LayoutInflater.from(tt9.getApplicationContext()).inflate(R.layout.mainview, null);
for (int buttonId : buttons) { for (int buttonId : buttons) {
view.findViewById(buttonId).setOnTouchListener(this); view.findViewById(buttonId).setOnTouchListener(this);
@ -31,10 +36,6 @@ class SoftKeyHandler implements View.OnTouchListener {
return view; return view;
} }
View getView() {
return view;
}
void show() { void show() {
if (view != null) { 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 @Override
public boolean onTouch(View view, MotionEvent event) { public boolean onTouch(View view, MotionEvent event) {
int action = event.getAction(); int action = event.getAction();
int buttonId = view.getId(); int buttonId = view.getId();
if (buttonId == R.id.main_left && action == MotionEvent.ACTION_UP) { if (buttonId == R.id.main_left && action == MotionEvent.ACTION_UP) {
UI.showPreferencesScreen(tt9); UI.showSettingsScreen(tt9);
return view.performClick(); return view.performClick();
} }

View file

@ -8,7 +8,7 @@ import android.text.style.UnderlineSpan;
import io.github.sspanak.tt9.Logger; import io.github.sspanak.tt9.Logger;
public class Util { public class TextHelper {
public static CharSequence highlightComposingText(CharSequence word, int start, int end) { public static CharSequence highlightComposingText(CharSequence word, int start, int end) {
if (end < start || start < 0) { if (end < start || start < 0) {
Logger.w("tt9.util.highlightComposingText", "Cannot highlight invalid composing text range: [" + start + ", " + end + "]"); Logger.w("tt9.util.highlightComposingText", "Cannot highlight invalid composing text range: [" + start + ", " + end + "]");

View file

@ -40,17 +40,24 @@ public class TraditionalT9 extends KeyPadHandler {
} }
private void loadPreferences() { private void loadSettings() {
mLanguage = LanguageCollection.getLanguage(prefs.getInputLanguage()); mLanguage = LanguageCollection.getLanguage(settings.getInputLanguage());
mEnabledLanguages = prefs.getEnabledLanguages(); mEnabledLanguages = settings.getEnabledLanguageIds();
mInputMode = InputMode.getInstance(prefs.getInputMode()); mInputMode = InputMode.getInstance(settings.getInputMode());
mInputMode.setTextCase(prefs.getTextCase()); mInputMode.setTextCase(settings.getTextCase());
} }
private void validateLanguages() { private void validateLanguages() {
mEnabledLanguages = InputModeValidator.validateEnabledLanguages(prefs, mEnabledLanguages); mEnabledLanguages = InputModeValidator.validateEnabledLanguages(settings, mEnabledLanguages);
mLanguage = InputModeValidator.validateLanguage(prefs, mLanguage, 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; self = this;
if (softKeyHandler == null) { if (softKeyHandler == null) {
softKeyHandler = new SoftKeyHandler(getLayoutInflater(), this); softKeyHandler = new SoftKeyHandler(this);
} }
if (mSuggestionView == null) { if (mSuggestionView == null) {
mSuggestionView = new SuggestionsView(softKeyHandler.getView()); mSuggestionView = new SuggestionsView(softKeyHandler.getView());
} }
loadPreferences(); loadSettings();
prefs.clearLastWord(); validateFunctionKeys();
settings.clearLastWord();
} }
protected void onRestart(EditorInfo inputField) { protected void onRestart(EditorInfo inputField) {
// in case we are back from Preferences screen, update the language list // in case we are back from Settings screen, update the language list
mEnabledLanguages = prefs.getEnabledLanguages(); mEnabledLanguages = settings.getEnabledLanguageIds();
validateLanguages(); validateLanguages();
// some input fields support only numbers or do not accept predictions // some input fields support only numbers or do not accept predictions
determineAllowedInputModes(inputField); 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. // Some modes may want to change the default text case based on grammar rules.
determineNextTextCase(); determineNextTextCase();
InputModeValidator.validateTextCase(prefs, mInputMode, prefs.getTextCase()); InputModeValidator.validateTextCase(settings, mInputMode, settings.getTextCase());
// build the UI // build the UI
clearSuggestions();
UI.updateStatusIcon(this, mLanguage, mInputMode); UI.updateStatusIcon(this, mLanguage, mInputMode);
clearSuggestions();
mSuggestionView.setDarkTheme(settings.getDarkTheme());
softKeyHandler.setDarkTheme(settings.getDarkTheme());
softKeyHandler.setSoftKeysVisibility(settings.getShowSoftKeys());
softKeyHandler.show(); softKeyHandler.show();
if (!isInputViewShown()) { if (!isInputViewShown()) {
showWindow(true); showWindow(true);
} }
@ -239,32 +253,42 @@ public class TraditionalT9 extends KeyPadHandler {
} }
protected boolean onKeyInputMode(boolean hold) { protected boolean onKeyAddWord() {
if (mEditing == EDITING_DIALER) {
return false;
}
if (hold) {
nextLang();
} else {
nextInputMode();
}
return true;
}
protected boolean onKeyOtherAction(boolean hold) {
if (mEditing == EDITING_NOSHOW || mEditing == EDITING_DIALER) { if (mEditing == EDITING_NOSHOW || mEditing == EDITING_DIALER) {
return false; 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; return true;
} }
@ -407,7 +431,7 @@ public class TraditionalT9 extends KeyPadHandler {
private void setComposingTextWithWordStemIndication(CharSequence word) { private void setComposingTextWithWordStemIndication(CharSequence word) {
if (mInputMode.getWordStem().length() > 0) { if (mInputMode.getWordStem().length() > 0) {
setComposingText(Util.highlightComposingText(word, 0, mInputMode.getWordStem().length())); setComposingText(TextHelper.highlightComposingText(word, 0, mInputMode.getWordStem().length()));
} else { } else {
setComposingText(word); setComposingText(word);
} }
@ -441,8 +465,8 @@ public class TraditionalT9 extends KeyPadHandler {
} }
// save the settings for the next time // save the settings for the next time
prefs.saveInputMode(mInputMode); settings.saveInputMode(mInputMode);
prefs.saveTextCase(mInputMode.getTextCase()); settings.saveTextCase(mInputMode.getTextCase());
UI.updateStatusIcon(this, mLanguage, mInputMode); UI.updateStatusIcon(this, mLanguage, mInputMode);
} }
@ -463,7 +487,7 @@ public class TraditionalT9 extends KeyPadHandler {
validateLanguages(); validateLanguages();
// save it for the next time // save it for the next time
prefs.saveInputLanguage(mLanguage.getId()); settings.saveInputLanguage(mLanguage.getId());
UI.updateStatusIcon(this, mLanguage, mInputMode); UI.updateStatusIcon(this, mLanguage, mInputMode);
} }
@ -483,7 +507,7 @@ public class TraditionalT9 extends KeyPadHandler {
private void determineAllowedInputModes(EditorInfo inputField) { private void determineAllowedInputModes(EditorInfo inputField) {
allowedInputModes = InputFieldHelper.determineInputModes(inputField); allowedInputModes = InputFieldHelper.determineInputModes(inputField);
int lastInputModeId = prefs.getInputMode(); int lastInputModeId = settings.getInputMode();
if (allowedInputModes.contains(lastInputModeId)) { if (allowedInputModes.contains(lastInputModeId)) {
mInputMode = InputMode.getInstance(lastInputModeId); mInputMode = InputMode.getInstance(lastInputModeId);
} else if (allowedInputModes.contains(InputMode.MODE_ABC)) { } 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. * If a new word was added to the dictionary, this function will append add it to the current input field.
*/ */
private void restoreAddedWordIfAny() { private void restoreAddedWordIfAny() {
String word = prefs.getLastWord(); String word = settings.getLastWord();
prefs.clearLastWord(); settings.clearLastWord();
if (word.length() == 0 || word.equals(InputFieldHelper.getSurroundingWord(currentInputConnection))) { if (word.length() == 0 || word.equals(InputFieldHelper.getSurroundingWord(currentInputConnection))) {
return; return;
@ -549,6 +573,6 @@ public class TraditionalT9 extends KeyPadHandler {
* Generates the actual UI of TT9. * Generates the actual UI of TT9.
*/ */
protected View createSoftKeyView() { protected View createSoftKeyView() {
return softKeyHandler.createView(getLayoutInflater()); return softKeyHandler.getView();
} }
} }

View file

@ -11,7 +11,7 @@ import io.github.sspanak.tt9.Logger;
import io.github.sspanak.tt9.db.DictionaryDb; import io.github.sspanak.tt9.db.DictionaryDb;
import io.github.sspanak.tt9.languages.Language; import io.github.sspanak.tt9.languages.Language;
import io.github.sspanak.tt9.languages.Punctuation; 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 class ModePredictive extends InputMode {
public int getId() { return MODE_PREDICTIVE; } public int getId() { return MODE_PREDICTIVE; }
@ -194,8 +194,8 @@ public class ModePredictive extends InputMode {
language, language,
digitSequence, digitSequence,
stem, stem,
T9Preferences.getInstance().getSuggestionsMin(), SettingsStore.getInstance().getSuggestionsMin(),
T9Preferences.getInstance().getSuggestionsMax() SettingsStore.getInstance().getSuggestionsMax()
); );
return true; return true;

View file

@ -1,5 +1,7 @@
package io.github.sspanak.tt9.languages; package io.github.sspanak.tt9.languages;
import androidx.annotation.NonNull;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Locale; import java.util.Locale;
@ -102,4 +104,10 @@ public class Language {
return sequence.toString(); return sequence.toString();
} }
@NonNull
@Override
public String toString() {
return name != null ? name : "";
}
} }

View file

@ -1,7 +1,10 @@
package io.github.sspanak.tt9.languages; package io.github.sspanak.tt9.languages;
import android.os.Build;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -57,7 +60,7 @@ public class LanguageCollection {
return null; return null;
} }
public static ArrayList<Language> getAll(ArrayList<Integer> languageIds) { public static ArrayList<Language> getAll(ArrayList<Integer> languageIds, boolean sort) {
ArrayList<Language> langList = new ArrayList<>(); ArrayList<Language> langList = new ArrayList<>();
for (int languageId : languageIds) { 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; return langList;
} }
public static ArrayList<Language> getAll(ArrayList<Integer> languageIds) {
return getAll(languageIds, false);
}
public static ArrayList<Language> getAll(boolean sort) {
ArrayList<Language> 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<Language> getAll() {
return getAll(false);
}
public static String toString(ArrayList<Language> 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();
}
} }

View file

@ -11,7 +11,7 @@ import io.github.sspanak.tt9.languages.Punctuation;
public class Bulgarian extends Language { public class Bulgarian extends Language {
public Bulgarian() { public Bulgarian() {
id = 7; id = 7;
name = "български"; name = "Български";
locale = new Locale("bg","BG"); locale = new Locale("bg","BG");
dictionaryFile = "bg-utf8.txt"; dictionaryFile = "bg-utf8.txt";
icon = R.drawable.ime_lang_bg; icon = R.drawable.ime_lang_bg;

View file

@ -11,7 +11,7 @@ import io.github.sspanak.tt9.languages.Punctuation;
public class Russian extends Language { public class Russian extends Language {
public Russian() { public Russian() {
id = 2; id = 2;
name = "русский"; name = "Русский";
locale = new Locale("ru","RU"); locale = new Locale("ru","RU");
dictionaryFile = "ru-utf8.txt"; dictionaryFile = "ru-utf8.txt";
icon = R.drawable.ime_lang_ru; icon = R.drawable.ime_lang_ru;

View file

@ -11,7 +11,7 @@ import io.github.sspanak.tt9.languages.Punctuation;
public class Ukrainian extends Language { public class Ukrainian extends Language {
public Ukrainian() { public Ukrainian() {
id = 6; id = 6;
name = "українська"; name = "Українська";
locale = new Locale("uk","UA"); locale = new Locale("uk","UA");
dictionaryFile = "uk-utf8.txt"; dictionaryFile = "uk-utf8.txt";
icon = R.drawable.ime_lang_uk; icon = R.drawable.ime_lang_uk;

View file

@ -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);
}

View file

@ -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<Language> 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));
}
}

View file

@ -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;
}
}

View file

@ -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<Language> languages = LanguageCollection.getAll(true);
ArrayList<CharSequence> values = new ArrayList<>();
for (Language l : languages) {
values.add(String.valueOf(l.getId()));
}
ArrayList<String> 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<String> newLanguages = (HashSet<String>) 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))
);
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}
}

View file

@ -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<String, String> KEYS = new LinkedHashMap<>();
private final Collection<DropDownPreference> items;
private final SettingsStore settings;
public SectionKeymap(Collection<DropDownPreference> 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<String> 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<String> 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;
}
}

View file

@ -8,6 +8,9 @@ import androidx.preference.PreferenceManager;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; 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.Logger;
import io.github.sspanak.tt9.ime.TraditionalT9; 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; import io.github.sspanak.tt9.languages.LanguageCollection;
public class T9Preferences { public class SettingsStore {
public static final int MAX_LANGUAGES = 32; private static SettingsStore self;
private static T9Preferences self;
private final SharedPreferences prefs; private final SharedPreferences prefs;
private final SharedPreferences.Editor prefsEditor; private final SharedPreferences.Editor prefsEditor;
public T9Preferences (Context context) { public SettingsStore(Context context) {
prefs = PreferenceManager.getDefaultSharedPreferences(context); prefs = PreferenceManager.getDefaultSharedPreferences(context);
prefsEditor = prefs.edit(); prefsEditor = prefs.edit();
} }
public static T9Preferences getInstance() { public static SettingsStore getInstance() {
if (self == null) { if (self == null) {
self = new T9Preferences(TraditionalT9.getMainContext()); self = new SettingsStore(TraditionalT9.getMainContext());
} }
return self; return self;
@ -45,21 +46,12 @@ public class T9Preferences {
return LanguageCollection.getLanguage(langId) != null; return LanguageCollection.getLanguage(langId) != null;
} }
private boolean isLanguageInRange(int langId) {
return langId > 0 && langId <= MAX_LANGUAGES;
}
private boolean validateSavedLanguage(int langId, String logTag) { private boolean validateSavedLanguage(int langId, String logTag) {
if (!doesLanguageExist(langId)) { if (!doesLanguageExist(langId)) {
Logger.w(logTag, "Not saving invalid language with ID: " + langId); Logger.w(logTag, "Not saving invalid language with ID: " + langId);
return false; 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; return true;
} }
@ -75,32 +67,47 @@ public class T9Preferences {
/************* input settings *************/ /************* input settings *************/
public ArrayList<Integer> getEnabledLanguages() { public ArrayList<Integer> getEnabledLanguageIds() {
int languageMask = prefs.getInt("pref_enabled_languages", 1); Set<String> languagesPref = getEnabledLanguagesIdsAsStrings();
ArrayList<Integer>languageIds = new ArrayList<>();
for (int langId = 1; langId < MAX_LANGUAGES; langId++) { ArrayList<Integer>languageIds = new ArrayList<>();
int maskBit = 1 << (langId - 1); for (String languageId : languagesPref) {
if ((maskBit & languageMask) != 0) { languageIds.add(Integer.valueOf(languageId));
languageIds.add(langId);
}
} }
return languageIds; return languageIds;
} }
public void saveEnabledLanguages(ArrayList<Integer> languageIds) { public Set<String> getEnabledLanguagesIdsAsStrings() {
int languageMask = 0; return prefs.getStringSet("pref_languages", new HashSet<>(Collections.singletonList("1")));
}
public void saveEnabledLanguageIds(ArrayList<Integer> languageIds) {
Set<String> idsAsStrings = new HashSet<>();
for (int langId : languageIds) { for (int langId : languageIds) {
if (!validateSavedLanguage(langId, "tt9/saveEnabledLanguages")){ idsAsStrings.add(String.valueOf(langId));
}
saveEnabledLanguageIds(idsAsStrings);
}
public void saveEnabledLanguageIds(Set<String> languageIds) {
Set<String> validLanguageIds = new HashSet<>();
for (String langId : languageIds) {
if (!validateSavedLanguage(Integer.parseInt(langId), "tt9/saveEnabledLanguageIds")){
continue; continue;
} }
int languageMaskBit = 1 << (langId - 1); validLanguageIds.add(langId);
languageMask |= languageMaskBit;
} }
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(); 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() { 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 *************/ /************* internal settings *************/
@ -177,7 +225,7 @@ public class T9Preferences {
} }
public void saveLastWord(String lastWord) { 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. // It is weird, but it is simple and it works, so I decided to keep it.
prefsEditor.putString("last_word", lastWord); prefsEditor.putString("last_word", lastWord);
prefsEditor.apply(); prefsEditor.apply();

View file

@ -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<Setting> inflate(Context context, int xmlFileResId, Object[] isettings)
throws Exception {
ArrayList<Setting> settings = new ArrayList<Setting>();
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;
}
}

View file

@ -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() {};
}

View file

@ -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<Setting> {
public SettingAdapter(Context context, ArrayList<Setting> 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;
}
}

View file

@ -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();
}
}

View file

@ -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<Integer> buildSelection(){
ArrayList<Integer> selection = new ArrayList<>();
for (int x=0;x<selectedEntries.length;x++) {
if (selectedEntries[x]) {
selection.add(entryValues[x]);
}
}
if (selection.size() < 1) {
selection.add(entryValues[0]);
}
return selection;
}
private String buildItems() {
StringBuilder sb = new StringBuilder();
for (int x=0;x<selectedEntries.length;x++) {
if (selectedEntries[x]) {
sb.append(entries[x]);
sb.append((", "));
}
}
if (sb.length() > 1)
sb.setLength(sb.length()-2);
return sb.toString();
}
}

View file

@ -1,6 +1,5 @@
package io.github.sspanak.tt9.ui; package io.github.sspanak.tt9.ui;
import android.app.Activity;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
@ -9,23 +8,31 @@ import android.os.Message;
import android.view.View; import android.view.View;
import android.widget.EditText; 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.Logger;
import io.github.sspanak.tt9.R; import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.db.DictionaryDb; import io.github.sspanak.tt9.db.DictionaryDb;
import io.github.sspanak.tt9.db.InsertBlankWordException; import io.github.sspanak.tt9.db.InsertBlankWordException;
import io.github.sspanak.tt9.languages.InvalidLanguageException; import io.github.sspanak.tt9.languages.InvalidLanguageException;
import io.github.sspanak.tt9.languages.LanguageCollection; 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; private View main;
int lang; private int lang;
String word; private String word;
@Override @Override
protected void onCreate(Bundle savedData) { protected void onCreate(Bundle savedData) {
AppCompatDelegate.setDefaultNightMode(
SettingsStore.getInstance().getDarkTheme() ? AppCompatDelegate.MODE_NIGHT_YES : AppCompatDelegate.MODE_NIGHT_NO
);
super.onCreate(savedData); super.onCreate(savedData);
Intent i = getIntent(); Intent i = getIntent();
word = i.getStringExtra("io.github.sspanak.tt9.word"); word = i.getStringExtra("io.github.sspanak.tt9.word");
lang = i.getIntExtra("io.github.sspanak.tt9.lang", -1); lang = i.getIntExtra("io.github.sspanak.tt9.lang", -1);
@ -46,7 +53,7 @@ public class AddWordAct extends Activity {
switch (msg.what) { switch (msg.what) {
case 0: case 0:
Logger.d("onAddedWord", "Added word: '" + word + "'..."); Logger.d("onAddedWord", "Added word: '" + word + "'...");
T9Preferences.getInstance().saveLastWord(word); SettingsStore.getInstance().saveLastWord(word);
break; break;
case 1: case 1:

View file

@ -21,6 +21,8 @@ import io.github.sspanak.tt9.languages.LanguageCollection;
public class DictionaryLoadingBar { public class DictionaryLoadingBar {
private static DictionaryLoadingBar self;
private static final int NOTIFICATION_ID = 1; private static final int NOTIFICATION_ID = 1;
private static final String NOTIFICATION_CHANNEL_ID = "loading-notifications"; private static final String NOTIFICATION_CHANNEL_ID = "loading-notifications";
@ -33,7 +35,16 @@ public class DictionaryLoadingBar {
private boolean hasFailed = false; 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(); resources = context.getResources();
manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
@ -46,6 +57,7 @@ public class DictionaryLoadingBar {
)); ));
notificationBuilder = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID); notificationBuilder = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID);
} else { } else {
//noinspection deprecation
notificationBuilder = new NotificationCompat.Builder(context); notificationBuilder = new NotificationCompat.Builder(context);
} }

View file

@ -13,17 +13,17 @@ import androidx.recyclerview.widget.RecyclerView;
import java.util.List; import java.util.List;
public class SuggestionsAdapter extends RecyclerView.Adapter<SuggestionsAdapter.ViewHolder> { public class SuggestionsAdapter extends RecyclerView.Adapter<SuggestionsAdapter.ViewHolder> {
private final int colorHighlight;
private final int layout; private final int layout;
private final int textViewResourceId; private final int textViewResourceId;
private final LayoutInflater mInflater; private final LayoutInflater mInflater;
private final List<String> mSuggestions; private final List<String> mSuggestions;
private int colorDefault;
private int colorHighlight;
private int selectedIndex = 0; private int selectedIndex = 0;
public SuggestionsAdapter(Context context, int layout, int textViewResourceId, int highLightColor, List<String> suggestions) { public SuggestionsAdapter(Context context, int layout, int textViewResourceId, List<String> suggestions) {
this.colorHighlight = highLightColor;
this.layout = layout; this.layout = layout;
this.textViewResourceId = textViewResourceId; this.textViewResourceId = textViewResourceId;
this.mInflater = LayoutInflater.from(context); this.mInflater = LayoutInflater.from(context);
@ -41,6 +41,7 @@ public class SuggestionsAdapter extends RecyclerView.Adapter<SuggestionsAdapter.
@Override @Override
public void onBindViewHolder(ViewHolder holder, int position) { public void onBindViewHolder(ViewHolder holder, int position) {
holder.suggestionItem.setText(mSuggestions.get(position)); holder.suggestionItem.setText(mSuggestions.get(position));
holder.suggestionItem.setTextColor(colorDefault);
holder.suggestionItem.setBackgroundColor(selectedIndex == position ? colorHighlight : Color.TRANSPARENT); holder.suggestionItem.setBackgroundColor(selectedIndex == position ? colorHighlight : Color.TRANSPARENT);
} }
@ -56,6 +57,16 @@ public class SuggestionsAdapter extends RecyclerView.Adapter<SuggestionsAdapter.
} }
public void setColorDefault(int colorDefault) {
this.colorDefault = colorDefault;
}
public void setColorHighlight(int colorHighlight) {
this.colorHighlight = colorHighlight;
}
public class ViewHolder extends RecyclerView.ViewHolder { public class ViewHolder extends RecyclerView.ViewHolder {
TextView suggestionItem; TextView suggestionItem;

View file

@ -15,7 +15,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import io.github.sspanak.tt9.R; import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.preferences.T9Preferences; import io.github.sspanak.tt9.preferences.SettingsStore;
public class SuggestionsView { public class SuggestionsView {
private final List<String> suggestions = new ArrayList<>(); private final List<String> suggestions = new ArrayList<>();
@ -40,8 +40,8 @@ public class SuggestionsView {
private void configureAnimation() { private void configureAnimation() {
DefaultItemAnimator animator = new DefaultItemAnimator(); DefaultItemAnimator animator = new DefaultItemAnimator();
int translateDuration = T9Preferences.getInstance().getSuggestionTranslateAnimationDuration(); int translateDuration = SettingsStore.getInstance().getSuggestionTranslateAnimationDuration();
int selectDuration = T9Preferences.getInstance().getSuggestionSelectAnimationDuration(); int selectDuration = SettingsStore.getInstance().getSuggestionSelectAnimationDuration();
animator.setMoveDuration(selectDuration); animator.setMoveDuration(selectDuration);
animator.setChangeDuration(translateDuration); animator.setChangeDuration(translateDuration);
@ -57,10 +57,11 @@ public class SuggestionsView {
context, context,
R.layout.suggestion_list_view, R.layout.suggestion_list_view,
R.id.suggestion_list_item, R.id.suggestion_list_item,
ContextCompat.getColor(context, R.color.candidate_selected),
suggestions suggestions
); );
mView.setAdapter(mSuggestionsAdapter); mView.setAdapter(mSuggestionsAdapter);
setDarkTheme(true); // just use some default colors
} }
@ -134,4 +135,28 @@ public class SuggestionsView {
mView.scrollToPosition(selectedIndex); 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));
}
} }

View file

@ -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<Language> 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));
}
}
}

View file

@ -9,6 +9,7 @@ import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.ime.TraditionalT9; import io.github.sspanak.tt9.ime.TraditionalT9;
import io.github.sspanak.tt9.ime.modes.InputMode; import io.github.sspanak.tt9.ime.modes.InputMode;
import io.github.sspanak.tt9.languages.Language; import io.github.sspanak.tt9.languages.Language;
import io.github.sspanak.tt9.preferences.PreferencesActivity;
public class UI { public class UI {
public static void showAddWordDialog(TraditionalT9 tt9, int language, String currentWord) { public static void showAddWordDialog(TraditionalT9 tt9, int language, String currentWord) {
@ -21,8 +22,8 @@ public class UI {
} }
public static void showPreferencesScreen(TraditionalT9 tt9) { public static void showSettingsScreen(TraditionalT9 tt9) {
Intent prefIntent = new Intent(tt9, TraditionalT9Settings.class); Intent prefIntent = new Intent(tt9, PreferencesActivity.class);
prefIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); prefIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
prefIntent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); prefIntent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
tt9.hideWindow(); tt9.hideWindow();

View file

@ -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<sa.length;x++) {
ia[x] = Integer.valueOf(sa[x]);
}
return ia;
}
}
/**
* Gets the entries values that are selected
*
* @return the selected entries values
*/
public CharSequence[] getCheckedValues() {
return unpack(getValue());
}
private void restoreCheckedEntries() {
CharSequence[] entryValues = getEntryValues();
// Explode the string read in sharedpreferences
CharSequence[] vals = unpack(getValue());
if (vals != null) {
List<CharSequence> 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<CharSequence> values = new ArrayList<CharSequence>();
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<CharSequence> joined) {
List<String> titles = new ArrayList<String>();
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);
}
}