Skip to content

Commit dddefc2

Browse files
committed
Add settings and icon
1 parent 8de9fca commit dddefc2

26 files changed

+461
-235
lines changed

app/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ dependencies {
4141
implementation("androidx.core:core-ktx:1.9.0")
4242
implementation("androidx.appcompat:appcompat:1.6.1")
4343
implementation("com.google.android.material:material:1.11.0")
44+
implementation("androidx.preference:preference-ktx:1.2.1")
4445
testImplementation("junit:junit:4.13.2")
4546
androidTestImplementation("androidx.test.ext:junit:1.1.5")
4647
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")

app/src/main/AndroidManifest.xml

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,36 +3,41 @@
33
xmlns:tools="http://schemas.android.com/tools">
44

55
<uses-permission android:name="android.permission.VIBRATE" />
6+
67
<application
78
android:allowBackup="true"
89
android:dataExtractionRules="@xml/data_extraction_rules"
910
android:fullBackupContent="@xml/backup_rules"
1011
android:icon="@mipmap/ic_launcher"
1112
android:label="@string/app_name"
12-
android:roundIcon="@mipmap/ic_launcher_round"
1313
android:supportsRtl="true"
1414
android:theme="@style/Theme.TitanPocketKeyboard"
1515
tools:targetApi="31">
16-
<service android:name=".InputMethodService"
16+
<activity
17+
android:name=".SettingsActivity"
18+
android:exported="true"
19+
android:label="@string/ime_settings">
20+
<intent-filter>
21+
<action android:name="android.intent.action.MAIN" />
22+
<category android:name="android.intent.category.LAUNCHER" />
23+
</intent-filter>
24+
</activity>
25+
26+
<service
27+
android:name=".InputMethodService"
28+
android:enabled="true"
29+
android:exported="true"
1730
android:label="@string/ime_label"
1831
android:permission="android.permission.BIND_INPUT_METHOD"
19-
android:enabled="true"
20-
android:exported="true">
32+
android:directBootAware="true">
2133
<intent-filter>
2234
<action android:name="android.view.InputMethod" />
2335
</intent-filter>
24-
<meta-data android:name="android.view.im"
36+
37+
<meta-data
38+
android:name="android.view.im"
2539
android:resource="@xml/method" />
2640
</service>
27-
<!-- FIXME: Implement a settings interface
28-
<activity android:name=".SettingsActivity"
29-
android:label="@string/ime_settings"
30-
android:exported="true">
31-
<intent-filter>
32-
<action android:name="android.intent.action.MAIN" />
33-
</intent-filter>
34-
</activity>
35-
-->
3641
</application>
3742

3843
</manifest>

app/src/main/java/io/github/oin/titanpocketkeyboard/InputMethodService.kt

Lines changed: 95 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@ import android.os.VibrationEffect
55
import android.os.Vibrator
66
import android.text.InputType
77
import android.text.TextUtils
8+
import android.util.Log
89
import android.view.InputDevice
910
import android.view.KeyCharacterMap
1011
import android.view.KeyEvent
11-
import android.view.View
1212
import android.view.inputmethod.EditorInfo
13+
import androidx.preference.PreferenceManager
1314
import java.util.Locale
1415
import android.inputmethodservice.InputMethodService as AndroidInputMethodService
1516

@@ -39,25 +40,70 @@ fun makeKeyEvent(original: KeyEvent, code: Int, metaState: Int, action: Int, sou
3940
return KeyEvent(original.downTime, original.eventTime, action, code, original.repeatCount, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, code, 0, source)
4041
}
4142

43+
val templates = hashMapOf(
44+
"fr" to hashMapOf(
45+
KeyEvent.KEYCODE_A to arrayOf('`', '^', '´', '¨', '~', MPSUBST_BYPASS),
46+
KeyEvent.KEYCODE_E to arrayOf('´', '`', '^', '¨', MPSUBST_BYPASS),
47+
KeyEvent.KEYCODE_I to arrayOf('^', '´', '¨', '`', MPSUBST_BYPASS),
48+
KeyEvent.KEYCODE_O to arrayOf('^', '´', '`', '¨', '~', MPSUBST_BYPASS),
49+
KeyEvent.KEYCODE_U to arrayOf('`', '^', '´', '¨', MPSUBST_BYPASS),
50+
KeyEvent.KEYCODE_C to arrayOf('ç', MPSUBST_BYPASS),
51+
KeyEvent.KEYCODE_SPACE to arrayOf(MPSUBST_STR_DOTSPACE)
52+
),
53+
"es" to hashMapOf(
54+
KeyEvent.KEYCODE_A to arrayOf('´', MPSUBST_BYPASS),
55+
KeyEvent.KEYCODE_E to arrayOf('´', MPSUBST_BYPASS),
56+
KeyEvent.KEYCODE_I to arrayOf('´', MPSUBST_BYPASS),
57+
KeyEvent.KEYCODE_O to arrayOf('´', MPSUBST_BYPASS),
58+
KeyEvent.KEYCODE_U to arrayOf('´', MPSUBST_BYPASS),
59+
KeyEvent.KEYCODE_SPACE to arrayOf(MPSUBST_STR_DOTSPACE)
60+
),
61+
"de" to hashMapOf(
62+
KeyEvent.KEYCODE_A to arrayOf('¨', MPSUBST_BYPASS),
63+
KeyEvent.KEYCODE_O to arrayOf('¨', MPSUBST_BYPASS),
64+
KeyEvent.KEYCODE_U to arrayOf('¨', MPSUBST_BYPASS),
65+
KeyEvent.KEYCODE_SPACE to arrayOf(MPSUBST_STR_DOTSPACE)
66+
),
67+
"pt" to hashMapOf(
68+
KeyEvent.KEYCODE_A to arrayOf('´', '^', '`', '~', MPSUBST_BYPASS),
69+
KeyEvent.KEYCODE_E to arrayOf('´', '^', MPSUBST_BYPASS),
70+
KeyEvent.KEYCODE_I to arrayOf('´', MPSUBST_BYPASS),
71+
KeyEvent.KEYCODE_O to arrayOf('´', '^', '~', MPSUBST_BYPASS),
72+
KeyEvent.KEYCODE_U to arrayOf('´', MPSUBST_BYPASS),
73+
KeyEvent.KEYCODE_C to arrayOf('ç', MPSUBST_BYPASS),
74+
KeyEvent.KEYCODE_SPACE to arrayOf(MPSUBST_STR_DOTSPACE)
75+
),
76+
"order1" to hashMapOf( // áàâäã
77+
KeyEvent.KEYCODE_A to arrayOf('´', '`', '^', '¨', '~', MPSUBST_BYPASS),
78+
KeyEvent.KEYCODE_E to arrayOf('´', '`', '^', '¨', '~', MPSUBST_BYPASS),
79+
KeyEvent.KEYCODE_I to arrayOf('´', '`', '^', '¨', '~', MPSUBST_BYPASS),
80+
KeyEvent.KEYCODE_O to arrayOf('´', '`', '^', '¨', '~', MPSUBST_BYPASS),
81+
KeyEvent.KEYCODE_U to arrayOf('´', '`', '^', '¨', '~', MPSUBST_BYPASS),
82+
KeyEvent.KEYCODE_SPACE to arrayOf(MPSUBST_STR_DOTSPACE)
83+
),
84+
"order2" to hashMapOf( // àáâäã
85+
KeyEvent.KEYCODE_A to arrayOf('`', '´', '^', '¨', '~', MPSUBST_BYPASS),
86+
KeyEvent.KEYCODE_E to arrayOf('`', '´', '^', '¨', '~', MPSUBST_BYPASS),
87+
KeyEvent.KEYCODE_I to arrayOf('`', '´', '^', '¨', '~', MPSUBST_BYPASS),
88+
KeyEvent.KEYCODE_O to arrayOf('`', '´', '^', '¨', '~', MPSUBST_BYPASS),
89+
KeyEvent.KEYCODE_U to arrayOf('`', '´', '^', '¨', '~', MPSUBST_BYPASS),
90+
KeyEvent.KEYCODE_SPACE to arrayOf(MPSUBST_STR_DOTSPACE)
91+
)
92+
)
93+
4294
class InputMethodService : AndroidInputMethodService() {
4395
private lateinit var vibrator: Vibrator
44-
private lateinit var inputView: View
4596
private val shift = Modifier()
4697
private val alt = Modifier()
4798
private val sym = SimpleModifier()
4899
private var lastShift = false
49100
private var lastAlt = false
50101
private var lastSym = false
102+
103+
private var autoCapitalize = false
104+
51105
private val multipress = MultipressController(arrayOf(
52-
hashMapOf(
53-
KeyEvent.KEYCODE_A to arrayOf('`', '^', '´', '¨', '~', MPSUBST_BYPASS),
54-
KeyEvent.KEYCODE_E to arrayOf('´', '`', '^', '¨', MPSUBST_BYPASS),
55-
KeyEvent.KEYCODE_I to arrayOf('^', '´', '¨', '`', MPSUBST_BYPASS),
56-
KeyEvent.KEYCODE_O to arrayOf('^', '´', '`', '¨', '~', MPSUBST_BYPASS),
57-
KeyEvent.KEYCODE_U to arrayOf('`', '^', '´', '¨', MPSUBST_BYPASS),
58-
KeyEvent.KEYCODE_C to arrayOf('ç', MPSUBST_BYPASS),
59-
KeyEvent.KEYCODE_SPACE to arrayOf(MPSUBST_STR_DOTSPACE)
60-
),
106+
templates["fr"]!!,
61107
hashMapOf(
62108
KeyEvent.KEYCODE_Q to arrayOf(MPSUBST_TOGGLE_ALT, '°', MPSUBST_TOGGLE_SHIFT, MPSUBST_BYPASS),
63109
KeyEvent.KEYCODE_W to arrayOf(MPSUBST_TOGGLE_ALT, '&', '', MPSUBST_TOGGLE_SHIFT, MPSUBST_BYPASS),
@@ -92,11 +138,19 @@ class InputMethodService : AndroidInputMethodService() {
92138
override fun onCreate() {
93139
super.onCreate()
94140
vibrator = getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
141+
142+
val preferences = PreferenceManager.getDefaultSharedPreferences(this)
143+
preferences.registerOnSharedPreferenceChangeListener { preferences, key ->
144+
updateFromPreferences()
145+
}
146+
updateFromPreferences()
95147
}
96148

97149
override fun onStartInput(attribute: EditorInfo?, restarting: Boolean) {
98150
super.onStartInput(attribute, restarting)
99151

152+
updateFromPreferences()
153+
100154
if(!sym.get()) {
101155
updateAutoCapitalization()
102156
}
@@ -233,16 +287,6 @@ class InputMethodService : AndroidInputMethodService() {
233287
*/
234288
private fun onSymKey(event: KeyEvent, pressed: Boolean): Boolean {
235289

236-
// Some keys have special handling
237-
// when(event.keyCode) {
238-
// in arrayOf(KeyEvent.KEYCODE_ENTER, KeyEvent.KEYCODE_DEL, KeyEvent.KEYCODE_CTRL_LEFT, KeyEvent.KEYCODE_CTRL_RIGHT, KeyEvent.KEYCODE_BACK) -> {
239-
// if(pressed) {
240-
// super.onKeyDown(event.keyCode, event)
241-
// } else {
242-
// super.onKeyUp(event.keyCode, event)
243-
// }
244-
// }
245-
// }
246290
// The SPACE key is equivalent to hitting Shift
247291
if(event.keyCode == KeyEvent.KEYCODE_SPACE) {
248292
if(pressed) {
@@ -388,6 +432,9 @@ class InputMethodService : AndroidInputMethodService() {
388432
* Update the Shift modifier state for auto-capitalization.
389433
*/
390434
private fun updateAutoCapitalization() {
435+
if(!autoCapitalize) {
436+
return
437+
}
391438
if(currentInputEditorInfo == null || currentInputConnection == null) {
392439
return
393440
}
@@ -433,4 +480,31 @@ class InputMethodService : AndroidInputMethodService() {
433480
updateAutoCapitalization()
434481
}
435482
}
483+
484+
/**
485+
* Update values from the preferences.
486+
*/
487+
private fun updateFromPreferences() {
488+
val preferences = PreferenceManager.getDefaultSharedPreferences(this)
489+
490+
autoCapitalize = preferences.getBoolean("AutoCapitalize", true)
491+
492+
val lockThreshold = preferences.getInt("ModifierLockThreshold", 250)
493+
shift.lockThreshold = lockThreshold
494+
alt.lockThreshold = lockThreshold
495+
sym.lockThreshold = lockThreshold
496+
497+
val nextThreshold = preferences.getInt("ModifierNextThreshold", 350)
498+
shift.nextThreshold = nextThreshold
499+
alt.nextThreshold = nextThreshold
500+
501+
multipress.multipressThreshold = preferences.getInt("MultipressThreshold", 750)
502+
multipress.ignoreFirstLevel = !preferences.getBoolean("UseFirstLevel", true)
503+
multipress.ignoreDotSpace = !preferences.getBoolean("DotSpace", true)
504+
505+
val templateId = preferences.getString("FirstLevelTemplate", "fr")
506+
if(templates.containsKey(templateId)) {
507+
multipress.substitutions[0] = templates[templateId]!!
508+
}
509+
}
436510
}

app/src/main/java/io/github/oin/titanpocketkeyboard/Modifier.kt

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,16 @@ package io.github.oin.titanpocketkeyboard
77
* - double press to lock until the next press of this modifier
88
*/
99
class Modifier {
10+
/**
11+
* The maximum time of a double press needed to lock a modifier, in milliseconds.
12+
*/
13+
var lockThreshold = 250
14+
15+
/**
16+
* The minimum time of a long press needed to apply the modifier until it is released, in milliseconds.
17+
*/
18+
var nextThreshold = 350
19+
1020
private var held = false
1121
private var lock = false
1222
private var next = false
@@ -35,7 +45,7 @@ class Modifier {
3545
held = true
3646

3747
val t = System.currentTimeMillis()
38-
if(t - lastTime < 250) {
48+
if(t - lastTime < lockThreshold) {
3949
lock = !lock
4050
preventNext = true
4151
} else {
@@ -46,7 +56,7 @@ class Modifier {
4656
}
4757
fun onKeyUp() {
4858
val t = System.currentTimeMillis()
49-
next = !lock && t - lastTime < 350 && !preventNext
59+
next = !lock && t - lastTime < nextThreshold && !preventNext
5060
preventNext = false
5161
held = false
5262
}
@@ -66,6 +76,8 @@ class Modifier {
6676
* - press to lock until the next press of this modifier
6777
*/
6878
class SimpleModifier {
79+
var lockThreshold = 350
80+
6981
private var held = false
7082
private var lock = false
7183
private var lastTime: Long = 0
@@ -95,7 +107,7 @@ class SimpleModifier {
95107
}
96108
fun onKeyUp() {
97109
val t = System.currentTimeMillis()
98-
if(t - lastTime > 350) {
110+
if(t - lastTime > lockThreshold) {
99111
lock = false
100112
}
101113
held = false

app/src/main/java/io/github/oin/titanpocketkeyboard/MultipressController.kt

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,20 @@ const val MPSUBST_STR_DOTSPACE = '\uFFF6'
4646
/**
4747
* A controller for the Multipress functionality
4848
*/
49-
class MultipressController(private val substitutions: Array<HashMap<Int, Array<Char>>>) {
49+
class MultipressController(val substitutions: Array<HashMap<Int, Array<Char>>>) {
50+
/**
51+
* The maximum time for a subsequent press to be interpreted as a multipress, in milliseconds.
52+
*/
53+
var multipressThreshold = 750
54+
/**
55+
* Whether to ignore the first level of multipresses.
56+
*/
57+
var ignoreFirstLevel = false
58+
/**
59+
* Whether to ignore `MPSUBST_STR_DOTSPACE`.
60+
*/
61+
var ignoreDotSpace = false
62+
5063
private var last: Int = 0
5164
private var lastTime: Long = 0
5265
private var count: Int = 1
@@ -71,8 +84,7 @@ class MultipressController(private val substitutions: Array<HashMap<Int, Array<C
7184
fun process(e: KeyEvent, metaState: Int): Char {
7285
val keyCode = e.keyCode
7386
val t = System.currentTimeMillis()
74-
if(last == keyCode && t - lastTime < 750) {
75-
last = keyCode
87+
if(last == keyCode && t - lastTime < multipressThreshold) {
7688
lastTime = t
7789

7890
if(e.repeatCount == 1) {
@@ -126,6 +138,13 @@ class MultipressController(private val substitutions: Array<HashMap<Int, Array<C
126138
count = 0
127139
}
128140

141+
if(substitution != MPSUBST_STR_DOTSPACE && ignoreFirstLevel && longPressCount == 0) {
142+
return MPSUBST_BYPASS
143+
}
144+
if(substitution == MPSUBST_STR_DOTSPACE && ignoreDotSpace) {
145+
return MPSUBST_BYPASS
146+
}
147+
129148
if(lastSubstitution == substitution) {
130149
return MPSUBST_NOTHING
131150
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package io.github.oin.titanpocketkeyboard
2+
3+
import android.content.DialogInterface
4+
import android.os.Bundle
5+
import android.widget.SeekBar
6+
import androidx.appcompat.app.AlertDialog
7+
import androidx.appcompat.app.AppCompatActivity
8+
import androidx.preference.ListPreference
9+
import androidx.preference.Preference
10+
import androidx.preference.PreferenceFragmentCompat
11+
import androidx.preference.PreferenceManager
12+
import androidx.preference.SeekBarPreference
13+
14+
class SettingsActivity : AppCompatActivity() {
15+
16+
override fun onCreate(savedInstanceState: Bundle?) {
17+
super.onCreate(savedInstanceState)
18+
setContentView(R.layout.settings_activity)
19+
if (savedInstanceState == null) {
20+
supportFragmentManager
21+
.beginTransaction()
22+
.replace(R.id.settings, SettingsFragment())
23+
.commit()
24+
}
25+
supportActionBar?.setDisplayHomeAsUpEnabled(true)
26+
}
27+
28+
class SettingsFragment : PreferenceFragmentCompat() {
29+
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
30+
setPreferencesFromResource(R.xml.preferences, rootKey)
31+
val context = activity
32+
if(context != null) {
33+
findPreference<Preference>("Reset")?.onPreferenceClickListener = Preference.OnPreferenceClickListener {
34+
AlertDialog.Builder(context)
35+
.setTitle("Reset settings")
36+
.setMessage("Do you really want to reset all the settings to their default value?")
37+
.setIcon(android.R.drawable.ic_dialog_alert)
38+
.setPositiveButton(android.R.string.yes, DialogInterface.OnClickListener { dialog, which ->
39+
val editor = PreferenceManager.getDefaultSharedPreferences(context).edit()
40+
editor.clear()
41+
editor.commit()
42+
setPreferencesFromResource(R.xml.preferences, rootKey)
43+
})
44+
.setNegativeButton(android.R.string.no, null)
45+
.show()
46+
true
47+
}
48+
}
49+
}
50+
}
51+
}

0 commit comments

Comments
 (0)