diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 8b343e3e..1de7bf99 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -7,6 +7,7 @@ Thank you for your pull request! 🚀
- [] This pull request is on a [separate branch](https://docs.github.com/en/get-started/quickstart/github-flow) and not the main branch
+- [] I have tested my code with the `./gradlew lintKotlin detekt test` command as directed in the [testing section of the contributing guide](https://github.com/scribe-org/Scribe-Data/blob/main/CONTRIBUTING.md#testing)
---
diff --git a/.github/workflows/pr_unit_test.yaml b/.github/workflows/pr_unit_test.yaml
new file mode 100644
index 00000000..4afc97d4
--- /dev/null
+++ b/.github/workflows/pr_unit_test.yaml
@@ -0,0 +1,27 @@
+name: pr_unit_test
+on:
+ pull_request:
+ branches:
+ - main
+ types: [opened, reopened, synchronize]
+
+jobs:
+ unit-test:
+ runs-on: ubuntu-latest
+ name: Run unit tests
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Setup JDK environment
+ uses: actions/setup-java@v4
+ with:
+ distribution: "zulu"
+ java-version: 17
+
+ - name: Setup Gradle
+ uses: gradle/actions/setup-gradle@v4
+
+ - name: Run tests
+ run: ./gradlew test
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 8d63cfa1..89940c68 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -15,6 +15,7 @@ If you have questions or would like to communicate with the team, please [join u
- [First steps as a contributor](#first-steps)
- [Learning the tech stack](#learning-the-tech)
- [Development environment](#dev-env)
+- [Testing](#testing)
- [Issues and projects](#issues-projects)
- [Bug reports](#bug-reports)
- [Feature requests](#feature-requests)
@@ -119,6 +120,24 @@ git remote add upstream https://github.com/scribe-org/Scribe-Android.git
> [!NOTE]
> Feel free to contact the team in the [Android room on Matrix](https://matrix.to/#/#ScribeAndroid:matrix.org) if you're having problems getting your environment setup!
+
+
+## Testing [`⇧`](#contents)
+
+Scribe-Android includes a testing suite that should be ran before all pull requests and subsequent commits. Please run the following in the project root:
+
+```bash
+# Run ktlint and detekt:
+./gradlew lintKotlin detekt
+./gradlew test
+```
+
+If you see that there are linting errors above, then please run the following command to hopefully fix them automatically:
+
+```bash
+ktlint --format
+```
+
# Issues and projects [`⇧`](#contents)
diff --git a/app/src/main/java/be/scri/activities/MainActivity.kt b/app/src/main/java/be/scri/activities/MainActivity.kt
index 94609f13..27872a77 100644
--- a/app/src/main/java/be/scri/activities/MainActivity.kt
+++ b/app/src/main/java/be/scri/activities/MainActivity.kt
@@ -24,8 +24,6 @@ class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private var englishKeyboardIME: EnglishKeyboardIME? = null
- fun getEnglishKeyboardIME(): EnglishKeyboardIME? = englishKeyboardIME
-
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
supportActionBar?.displayOptions = androidx.appcompat.app.ActionBar.DISPLAY_SHOW_CUSTOM
@@ -221,6 +219,19 @@ class MainActivity : AppCompatActivity() {
}
}
+ override fun onBackPressed() {
+ super.onBackPressed()
+ if (viewPager.currentItem == 0) {
+ if (binding.fragmentContainer.visibility == View.VISIBLE) {
+ binding.fragmentContainer.visibility = View.GONE
+ } else {
+ finish()
+ }
+ } else {
+ viewPager.currentItem = viewPager.currentItem - 1
+ }
+ }
+
fun hideHint() {
val hintLayout = findViewById(R.id.hint_layout)
hintLayout.visibility = View.GONE
diff --git a/app/src/main/java/be/scri/extensions/ContextStyling.kt b/app/src/main/java/be/scri/extensions/ContextStyling.kt
index 4c2005b0..c7e5e8d6 100644
--- a/app/src/main/java/be/scri/extensions/ContextStyling.kt
+++ b/app/src/main/java/be/scri/extensions/ContextStyling.kt
@@ -35,8 +35,14 @@ fun Context.getProperPrimaryColor() =
else -> baseConfig.primaryColor
}
-fun Context.isBlackAndWhiteTheme() = baseConfig.textColor == Color.WHITE && baseConfig.primaryColor == Color.BLACK && baseConfig.backgroundColor == Color.BLACK
-
-fun Context.isWhiteTheme() = baseConfig.textColor == DARK_GREY && baseConfig.primaryColor == Color.WHITE && baseConfig.backgroundColor == Color.WHITE
+fun Context.isBlackAndWhiteTheme() =
+ baseConfig.textColor == Color.WHITE &&
+ baseConfig.primaryColor == Color.BLACK &&
+ baseConfig.backgroundColor == Color.BLACK
+
+fun Context.isWhiteTheme() =
+ baseConfig.textColor == DARK_GREY &&
+ baseConfig.primaryColor == Color.WHITE &&
+ baseConfig.backgroundColor == Color.WHITE
fun Context.isUsingSystemDarkTheme() = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_YES != 0
diff --git a/app/src/main/java/be/scri/fragments/AboutFragment.kt b/app/src/main/java/be/scri/fragments/AboutFragment.kt
index 09733f0a..cd785dcd 100644
--- a/app/src/main/java/be/scri/fragments/AboutFragment.kt
+++ b/app/src/main/java/be/scri/fragments/AboutFragment.kt
@@ -133,9 +133,8 @@ class AboutFragment : Fragment() {
),
)
- private fun getSecondRecyclerViewData(): List {
- val context = requireContext()
- return listOf(
+ private fun getSecondRecyclerViewData(): List =
+ listOf(
ItemsViewModel(
image = R.drawable.star,
text = ItemsViewModel.Text(R.string.app_about_feedback_rate_scribe),
@@ -177,7 +176,6 @@ class AboutFragment : Fragment() {
action = ::resetHints,
),
)
- }
private fun getThirdRecyclerViewData(): List =
listOf(
diff --git a/app/src/main/java/be/scri/fragments/LanguageSettingsFragment.kt b/app/src/main/java/be/scri/fragments/LanguageSettingsFragment.kt
index fb570c4e..7236cfb3 100644
--- a/app/src/main/java/be/scri/fragments/LanguageSettingsFragment.kt
+++ b/app/src/main/java/be/scri/fragments/LanguageSettingsFragment.kt
@@ -19,6 +19,7 @@ import be.scri.databinding.FragmentLanguageSettingsBinding
import be.scri.helpers.CustomAdapter
import be.scri.models.SwitchItem
+@Suppress("LongMethod")
class LanguageSettingsFragment : Fragment() {
private var _binding: FragmentLanguageSettingsBinding? = null
val binding get() = _binding!!
@@ -100,7 +101,11 @@ class LanguageSettingsFragment : Fragment() {
private fun setupRecyclerView(language: String) {
binding.functionalityRecyclerView.layoutManager = LinearLayoutManager(context)
- binding.functionalityRecyclerView.adapter = CustomAdapter(getFunctionalityRecyclerViewData(language), requireContext())
+ binding.functionalityRecyclerView.adapter =
+ CustomAdapter(
+ getFunctionalityRecyclerViewData(language),
+ requireContext(),
+ )
binding.layoutRecyclerView.layoutManager = LinearLayoutManager(context)
binding.layoutRecyclerView.adapter = CustomAdapter(getLayoutRecyclerViewData(language), requireContext())
@@ -137,9 +142,19 @@ class LanguageSettingsFragment : Fragment() {
"German" -> {
list.add(
SwitchItem(
- isChecked = sharedPref.getBoolean("disable_accent_character_$language", false),
- title = getString(R.string.app_settings_keyboard_layout_disable_accent_characters),
- description = getString(R.string.app_settings_keyboard_layout_disable_accent_characters_description),
+ isChecked =
+ sharedPref.getBoolean(
+ "disable_accent_character_$language",
+ false,
+ ),
+ title =
+ getString(
+ R.string.app_settings_keyboard_layout_disable_accent_characters,
+ ),
+ description =
+ getString(
+ R.string.app_settings_keyboard_layout_disable_accent_characters_description,
+ ),
action = { disableAccentCharacter(language) },
action2 = { enableAccentCharacters(language) },
),
@@ -148,9 +163,19 @@ class LanguageSettingsFragment : Fragment() {
"Swedish" -> {
list.add(
SwitchItem(
- isChecked = sharedPref.getBoolean("disable_accent_character_$language", false),
- title = getString(R.string.app_settings_keyboard_layout_disable_accent_characters),
- description = getString(R.string.app_settings_keyboard_layout_disable_accent_characters_description),
+ isChecked =
+ sharedPref.getBoolean(
+ "disable_accent_character_$language",
+ false,
+ ),
+ title =
+ getString(
+ R.string.app_settings_keyboard_layout_disable_accent_characters,
+ ),
+ description =
+ getString(
+ R.string.app_settings_keyboard_layout_disable_accent_characters_description,
+ ),
action = { disableAccentCharacter(language) },
action2 = { enableAccentCharacters(language) },
),
@@ -159,9 +184,19 @@ class LanguageSettingsFragment : Fragment() {
"Spanish" -> {
list.add(
SwitchItem(
- isChecked = sharedPref.getBoolean("disable_accent_character_$language", false),
- title = getString(R.string.app_settings_keyboard_layout_disable_accent_characters),
- description = getString(R.string.app_settings_keyboard_layout_disable_accent_characters_description),
+ isChecked =
+ sharedPref.getBoolean(
+ "disable_accent_character_$language",
+ false,
+ ),
+ title =
+ getString(
+ R.string.app_settings_keyboard_layout_disable_accent_characters,
+ ),
+ description =
+ getString(
+ R.string.app_settings_keyboard_layout_disable_accent_characters_description,
+ ),
action = { disableAccentCharacter(language) },
action2 = { enableAccentCharacters(language) },
),
diff --git a/app/src/main/java/be/scri/fragments/MainFragment.kt b/app/src/main/java/be/scri/fragments/MainFragment.kt
index d153144c..520713ae 100644
--- a/app/src/main/java/be/scri/fragments/MainFragment.kt
+++ b/app/src/main/java/be/scri/fragments/MainFragment.kt
@@ -29,7 +29,7 @@ class MainFragment : Fragment() {
// binding.scribeKey.setOnClickListener {
// (requireActivity().getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager).showInputMethodPicker()
// }
- binding.keyboardSettings.setOnClickListener {
+ binding.cardView.setOnClickListener {
openKeyboardSettings()
}
(requireActivity() as MainActivity).unsetActionBarLayoutMargin()
diff --git a/app/src/main/java/be/scri/fragments/PrivacyPolicyFragment.kt b/app/src/main/java/be/scri/fragments/PrivacyPolicyFragment.kt
index 407343ae..b765e620 100644
--- a/app/src/main/java/be/scri/fragments/PrivacyPolicyFragment.kt
+++ b/app/src/main/java/be/scri/fragments/PrivacyPolicyFragment.kt
@@ -29,7 +29,12 @@ class PrivacyPolicyFragment : Fragment() {
(requireActivity() as MainActivity).setActionBarButtonVisible()
(requireActivity() as MainActivity).setActionBarTitle(R.string.app_about_legal_privacy_policy)
(requireActivity() as MainActivity).setActionBarLayoutMargin()
- val textView = (requireActivity() as MainActivity).supportActionBar?.customView?.findViewById(R.id.name)
+ val textView =
+ (requireActivity() as MainActivity)
+ .supportActionBar
+ ?.customView
+ ?.findViewById(R.id.name)
+
(requireActivity() as MainActivity)
.supportActionBar
?.customView
diff --git a/app/src/main/java/be/scri/fragments/SettingsFragment.kt b/app/src/main/java/be/scri/fragments/SettingsFragment.kt
index bb1bd915..012d83e9 100644
--- a/app/src/main/java/be/scri/fragments/SettingsFragment.kt
+++ b/app/src/main/java/be/scri/fragments/SettingsFragment.kt
@@ -20,6 +20,7 @@ import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.content.res.AppCompatResources.getDrawable
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
import be.scri.R
import be.scri.activities.MainActivity
import be.scri.databinding.FragmentSettingsBinding
@@ -30,6 +31,7 @@ import be.scri.models.TextItem
class SettingsFragment : Fragment() {
private lateinit var binding: FragmentSettingsBinding
+ private var isDecorationSet: Boolean = false
override fun onCreateView(
inflater: LayoutInflater,
@@ -141,24 +143,31 @@ class SettingsFragment : Fragment() {
setupItemVisibility()
}
}
-
val recyclerView = binding.recyclerView2
val adapter = CustomAdapter(getRecyclerViewElements(), requireContext())
recyclerView.layoutManager = LinearLayoutManager(requireContext())
recyclerView.adapter = adapter
recyclerView.suppressLayout(true)
- recyclerView.apply {
- val itemDecoration =
- CustomDividerItemDecoration(
- drawable = getDrawable(requireContext(), R.drawable.rv_divider)!!,
- width = 1,
- marginLeft = 50,
- marginRight = 50,
- )
- addItemDecoration(itemDecoration)
+
+ if (!isDecorationSet) {
+ isDecorationSet = true
+ recyclerView.apply {
+ addCustomItemDecoration()
+ }
}
}
+ private fun RecyclerView.addCustomItemDecoration() {
+ val itemDecoration =
+ CustomDividerItemDecoration(
+ drawable = getDrawable(requireContext(), R.drawable.rv_divider)!!,
+ width = 1,
+ marginLeft = 50,
+ marginRight = 50,
+ )
+ addItemDecoration(itemDecoration)
+ }
+
private fun getRecyclerViewElements(): MutableList {
val languages = setupKeyboardLanguage()
val list = mutableListOf()
diff --git a/app/src/main/java/be/scri/helpers/MyKeyboard.kt b/app/src/main/java/be/scri/helpers/MyKeyboard.kt
index 982097bd..177a3ed1 100644
--- a/app/src/main/java/be/scri/helpers/MyKeyboard.kt
+++ b/app/src/main/java/be/scri/helpers/MyKeyboard.kt
@@ -21,6 +21,7 @@ import java.io.IOException
* @attr ref android.R.styleable#Keyboard_keyWidth
* @attr ref android.R.styleable#Keyboard_horizontalGap
*/
+@Suppress("LongMethod")
class MyKeyboard {
/** Horizontal gap default for all rows */
private var mDefaultHorizontalGap = 0
@@ -80,7 +81,9 @@ class MyKeyboard {
}
/**
- * Container for keys in the keyboard. All keys in a row are at the same Y-coordinate. Some of the key size defaults can be overridden per row from
+ * Container for keys in the keyboard.
+ * All keys in a row are at the same Y-coordinate.
+ * Some of the key size defaults can be overridden per row from
* what the [MyKeyboard] defines.
* @attr ref android.R.styleable#Keyboard_keyWidth
* @attr ref android.R.styleable#Keyboard_horizontalGap
@@ -106,9 +109,26 @@ class MyKeyboard {
constructor(res: Resources, parent: MyKeyboard, parser: XmlResourceParser?) {
this.parent = parent
val a = res.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.MyKeyboard)
- defaultWidth = getDimensionOrFraction(a, R.styleable.MyKeyboard_keyWidth, parent.mDisplayWidth, parent.mDefaultWidth)
- defaultHeight = res.getDimension(R.dimen.key_height).toInt()
- defaultHorizontalGap = getDimensionOrFraction(a, R.styleable.MyKeyboard_horizontalGap, parent.mDisplayWidth, parent.mDefaultHorizontalGap)
+ defaultWidth =
+ getDimensionOrFraction(
+ a,
+ R.styleable.MyKeyboard_keyWidth,
+ parent.mDisplayWidth,
+ parent.mDefaultWidth,
+ )
+
+ defaultHeight =
+ res
+ .getDimension(R.dimen.key_height)
+ .toInt()
+
+ defaultHorizontalGap =
+ getDimensionOrFraction(
+ a,
+ R.styleable.MyKeyboard_horizontalGap,
+ parent.mDisplayWidth,
+ parent.mDefaultHorizontalGap,
+ )
a.recycle()
}
}
@@ -167,7 +187,8 @@ class MyKeyboard {
var popupCharacters: CharSequence? = null
/**
- * Flags that specify the anchoring to edges of the keyboard for detecting touch events that are just out of the boundary of the key.
+ * Flags that specify the anchoring to edges of the keyboard for detecting touch events,
+ * that are just out of the boundary of the key.
* This is a bit mask of [MyKeyboard.EDGE_LEFT], [MyKeyboard.EDGE_RIGHT].
*/
private var edgeFlags = 0
@@ -191,10 +212,29 @@ class MyKeyboard {
constructor(res: Resources, parent: Row, x: Int, y: Int, parser: XmlResourceParser?) : this(parent) {
this.x = x
this.y = y
- var a = res.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.MyKeyboard)
- width = getDimensionOrFraction(a, R.styleable.MyKeyboard_keyWidth, keyboard.mDisplayWidth, parent.defaultWidth)
+ var a =
+ res.obtainAttributes(
+ Xml.asAttributeSet(parser),
+ R.styleable.MyKeyboard,
+ )
+
+ width =
+ getDimensionOrFraction(
+ a,
+ R.styleable.MyKeyboard_keyWidth,
+ keyboard.mDisplayWidth,
+ parent.defaultWidth,
+ )
+
height = parent.defaultHeight
- gap = getDimensionOrFraction(a, R.styleable.MyKeyboard_horizontalGap, keyboard.mDisplayWidth, parent.defaultHorizontalGap)
+
+ gap =
+ getDimensionOrFraction(
+ a,
+ R.styleable.MyKeyboard_horizontalGap,
+ keyboard.mDisplayWidth,
+ parent.defaultHorizontalGap,
+ )
this.x += gap
a.recycle()
@@ -228,7 +268,8 @@ class MyKeyboard {
* Detects if a point falls inside this key.
* @param x the x-coordinate of the point
* @param y the y-coordinate of the point
- * @return whether or not the point falls inside the key. If the key is attached to an edge, it will assume that all points between the key and
+ * @return whether or not the point falls inside the key.
+ * If the key is attached to an edge, it will assume that all points between the key and
* the edge are considered to be inside the key.
*/
fun isInside(
@@ -247,7 +288,8 @@ class MyKeyboard {
}
/**
- * Creates a keyboard from the given xml key layout file. Weeds out rows that have a keyboard mode defined but don't match the specified mode.
+ * Creates a keyboard from the given xml key layout file.
+ * Weeds out rows that have a keyboard mode defined but don't match the specified mode.
* @param context the application or service context
* @param xmlLayoutResId the resource file that contains the keyboard layout and keys.
* @param enterKeyType determines what icon should we show on Enter key
@@ -268,8 +310,10 @@ class MyKeyboard {
}
/**
- * Creates a blank keyboard from the given resource file and populates it with the specified characters in left-to-right, top-to-bottom fashion,
- * using the specified number of columns. If the specified number of columns is -1, then the keyboard will fit as many keys as possible in each row.
+ * Creates a blank keyboard from the given resource file and
+ * populates it with the specified characters in left-to-right, top-to-bottom fashion,
+ * using the specified number of columns. If the specified number of columns is -1,
+ * then the keyboard will fit as many keys as possible in each row.
* @param context the application or service context
* @param layoutTemplateResId the layout template file, containing no keys.
* @param characters the list of characters to display on the keyboard. One key will be created for each character.
@@ -365,10 +409,16 @@ class MyKeyboard {
if (key.code == KEYCODE_ENTER) {
val enterResourceId =
when (mEnterKeyType) {
- EditorInfo.IME_ACTION_SEARCH -> R.drawable.ic_search_vector
- EditorInfo.IME_ACTION_NEXT, EditorInfo.IME_ACTION_GO -> R.drawable.ic_arrow_right_vector
- EditorInfo.IME_ACTION_SEND -> R.drawable.ic_send_vector
- else -> R.drawable.ic_enter_vector
+ EditorInfo.IME_ACTION_SEARCH ->
+ R.drawable.ic_search_vector
+ EditorInfo.IME_ACTION_NEXT,
+ EditorInfo.IME_ACTION_GO,
+ ->
+ R.drawable.ic_arrow_right_vector
+ EditorInfo.IME_ACTION_SEND ->
+ R.drawable.ic_send_vector
+ else ->
+ R.drawable.ic_enter_vector
}
key.icon = context.resources.getDrawable(enterResourceId, context.theme)
}
diff --git a/app/src/main/java/be/scri/services/SimpleKeyboardIME.kt b/app/src/main/java/be/scri/services/SimpleKeyboardIME.kt
index 882e7234..c7fa7177 100644
--- a/app/src/main/java/be/scri/services/SimpleKeyboardIME.kt
+++ b/app/src/main/java/be/scri/services/SimpleKeyboardIME.kt
@@ -291,8 +291,12 @@ abstract class SimpleKeyboardIME(
emojiBtnTablet3 = binding.emojiBtnTablet3
}
- private fun updateButtonVisibility(isAutoSuggestEnabled: Boolean) {
- val isTablet = (resources.configuration.screenLayout and Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE
+ fun updateButtonVisibility(isAutoSuggestEnabled: Boolean) {
+ val isTablet =
+ (
+ resources.configuration.screenLayout and
+ Configuration.SCREENLAYOUT_SIZE_MASK
+ ) >= Configuration.SCREENLAYOUT_SIZE_LARGE
if (isTablet) {
pluralBtn?.visibility = if (isAutoSuggestEnabled) View.INVISIBLE else View.VISIBLE
emojiBtnTablet1?.visibility = if (isAutoSuggestEnabled) View.VISIBLE else View.INVISIBLE
@@ -345,7 +349,11 @@ abstract class SimpleKeyboardIME(
fun updateShiftKeyState() {
if (keyboardMode == keyboardLetters) {
val editorInfo = currentInputEditorInfo
- if (editorInfo != null && editorInfo.inputType != InputType.TYPE_NULL && keyboard?.mShiftState != SHIFT_ON_PERMANENT) {
+ if (
+ editorInfo != null &&
+ editorInfo.inputType != InputType.TYPE_NULL &&
+ keyboard?.mShiftState != SHIFT_ON_PERMANENT
+ ) {
if (currentInputConnection.getCursorCapsMode(editorInfo.inputType) != 0) {
keyboard?.setShifted(SHIFT_ON_ONE_CHAR)
keyboardView?.invalidateAllKeys()
@@ -360,7 +368,11 @@ abstract class SimpleKeyboardIME(
keyboard = MyKeyboard(this, getKeyboardLayoutXML(), enterKeyType)
val editorInfo = currentInputEditorInfo
- if (editorInfo != null && editorInfo.inputType != InputType.TYPE_NULL && keyboard?.mShiftState != SHIFT_ON_PERMANENT) {
+ if (
+ editorInfo != null &&
+ editorInfo.inputType != InputType.TYPE_NULL &&
+ keyboard?.mShiftState != SHIFT_ON_PERMANENT
+ ) {
if (currentInputConnection.getCursorCapsMode(editorInfo.inputType) != 0) {
keyboard?.setShifted(SHIFT_ON_ONE_CHAR)
}
@@ -447,10 +459,18 @@ abstract class SimpleKeyboardIME(
) {
if (keyboardMode == keyboardLetters) {
when {
- keyboard!!.mShiftState == SHIFT_ON_PERMANENT -> keyboard!!.mShiftState = SHIFT_OFF
- System.currentTimeMillis() - lastShiftPressTS < shiftPermToggleSpeed -> keyboard!!.mShiftState = SHIFT_ON_PERMANENT
- keyboard!!.mShiftState == SHIFT_ON_ONE_CHAR -> keyboard!!.mShiftState = SHIFT_OFF
- keyboard!!.mShiftState == SHIFT_OFF -> keyboard!!.mShiftState = SHIFT_ON_ONE_CHAR
+ keyboard!!.mShiftState == SHIFT_ON_PERMANENT -> {
+ keyboard!!.mShiftState = SHIFT_OFF
+ }
+ System.currentTimeMillis() - lastShiftPressTS < shiftPermToggleSpeed -> {
+ keyboard!!.mShiftState = SHIFT_ON_PERMANENT
+ }
+ keyboard!!.mShiftState == SHIFT_ON_ONE_CHAR -> {
+ keyboard!!.mShiftState = SHIFT_OFF
+ }
+ keyboard!!.mShiftState == SHIFT_OFF -> {
+ keyboard!!.mShiftState = SHIFT_ON_ONE_CHAR
+ }
}
lastShiftPressTS = System.currentTimeMillis()
diff --git a/app/src/main/java/be/scri/views/CustomDividerItemDecoration.kt b/app/src/main/java/be/scri/views/CustomDividerItemDecoration.kt
index 35c80c9a..ca789127 100644
--- a/app/src/main/java/be/scri/views/CustomDividerItemDecoration.kt
+++ b/app/src/main/java/be/scri/views/CustomDividerItemDecoration.kt
@@ -2,7 +2,6 @@ import android.graphics.Canvas
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.view.View
-import androidx.annotation.NonNull
import androidx.recyclerview.widget.RecyclerView
class CustomDividerItemDecoration(
@@ -12,15 +11,15 @@ class CustomDividerItemDecoration(
private val marginRight: Int,
) : RecyclerView.ItemDecoration() {
override fun onDraw(
- @NonNull canvas: Canvas,
- @NonNull parent: RecyclerView,
- @NonNull state: RecyclerView.State,
+ canvas: Canvas,
+ parent: RecyclerView,
+ state: RecyclerView.State,
) {
val left = parent.paddingLeft + marginLeft
val right = parent.width - parent.paddingRight - marginRight
val childCount = parent.childCount
- for (i in 0 until childCount - 1) { // Exclude the last item
+ for (i in 0 until childCount - 1) {
val child = parent.getChildAt(i)
val params = child.layoutParams as RecyclerView.LayoutParams
val top = child.bottom + params.bottomMargin
diff --git a/app/src/main/java/be/scri/views/MyKeyboardView.kt b/app/src/main/java/be/scri/views/MyKeyboardView.kt
index 2e530b4a..4f1a7932 100644
--- a/app/src/main/java/be/scri/views/MyKeyboardView.kt
+++ b/app/src/main/java/be/scri/views/MyKeyboardView.kt
@@ -58,7 +58,7 @@ import java.util.Arrays
import java.util.Locale
@SuppressLint("UseCompatLoadingForDrawables")
-@Suppress("LargeClass")
+@Suppress("LargeClass", "LongMethod")
class MyKeyboardView
@JvmOverloads
constructor(
@@ -68,8 +68,10 @@ class MyKeyboardView
) : View(context, attrs, defStyleRes) {
interface OnKeyboardActionListener {
/**
- * Called when the user presses a key. This is sent before the [.onKey] is called. For keys that repeat, this is only called once.
- * @param primaryCode the unicode of the key being pressed. If the touch is not on a valid key, the value will be zero.
+ * Called when the user presses a key. This is sent before the [.onKey] is called.
+ * For keys that repeat, this is only called once.
+ * @param primaryCode the unicode of the key being pressed.
+ * If the touch is not on a valid key, the value will be zero.
*/
fun onPress(primaryCode: Int)
@@ -280,9 +282,10 @@ class MyKeyboardView
try {
for (i in 0 until indexCnt) {
-
when (val attr = attributes.getIndex(i)) {
- R.styleable.MyKeyboardView_keyTextSize -> mKeyTextSize = attributes.getDimensionPixelSize(attr, 18)
+ R.styleable.MyKeyboardView_keyTextSize -> {
+ mKeyTextSize = attributes.getDimensionPixelSize(attr, 18)
+ }
}
}
} finally {
@@ -379,8 +382,15 @@ class MyKeyboardView
if (changedView == popupBinding.miniKeyboardView) {
val previewBackground = background as LayerDrawable
- previewBackground.findDrawableByLayerId(R.id.button_background_shape).applyColorFilter(miniKeyboardBackgroundColor)
- previewBackground.findDrawableByLayerId(R.id.button_background_stroke).applyColorFilter(strokeColor)
+
+ previewBackground
+ .findDrawableByLayerId(R.id.button_background_shape)
+ .applyColorFilter(miniKeyboardBackgroundColor)
+
+ previewBackground
+ .findDrawableByLayerId(R.id.button_background_stroke)
+ .applyColorFilter(strokeColor)
+
background = previewBackground
} else {
background.applyColorFilter(darkerColor)
@@ -399,7 +409,8 @@ class MyKeyboardView
}
/**
- * Attaches a keyboard to this view. The keyboard can be switched at any time and the view will re-layout itself to accommodate the keyboard.
+ * Attaches a keyboard to this view.
+ * The keyboard can be switched at any time and the view will re-layout itself to accommodate the keyboard.
* @param keyboard the keyboard to display in this view
*/
fun setKeyboard(keyboard: MyKeyboard) {
@@ -468,7 +479,13 @@ class MyKeyboardView
private fun adjustCase(label: CharSequence): CharSequence? {
var newLabel: CharSequence? = label
- if (newLabel != null && newLabel.isNotEmpty() && mKeyboard!!.mShiftState > SHIFT_OFF && newLabel.length < 3 && Character.isLowerCase(newLabel[0])) {
+ if (
+ newLabel != null &&
+ newLabel.isNotEmpty() &&
+ mKeyboard!!.mShiftState > SHIFT_OFF &&
+ newLabel.length < 3 &&
+ Character.isLowerCase(newLabel[0])
+ ) {
newLabel = newLabel.toString().uppercase(Locale.getDefault())
}
return newLabel
@@ -490,8 +507,9 @@ class MyKeyboardView
}
/**
- * Compute the average distance between adjacent keys (horizontally and vertically) and square it to get the proximity threshold. We use a square here and
- * in computing the touch distance from a key's center to avoid taking a square root.
+ * Compute the average distance between adjacent keys (horizontally and vertically)
+ * and square it to get the proximity threshold.
+ * We use a square here and in computing the touch distance from a key's center to avoid taking a square root.
* @param keyboard
*/
private fun computeProximityThreshold(keyboard: MyKeyboard?) {
@@ -671,7 +689,12 @@ class MyKeyboardView
)
if (key.topSmallNumber.isNotEmpty()) {
- canvas.drawText(key.topSmallNumber, key.width - mTopSmallNumberMarginWidth, mTopSmallNumberMarginHeight, smallLetterPaint)
+ canvas.drawText(
+ key.topSmallNumber,
+ key.width - mTopSmallNumberMarginWidth,
+ mTopSmallNumberMarginHeight,
+ smallLetterPaint,
+ )
}
// Turn off drop shadow.
@@ -747,14 +770,23 @@ class MyKeyboardView
oldKey.pressed = false
invalidateKey(oldKeyIndex)
val keyCode = oldKey.code
- sendAccessibilityEventForUnicodeCharacter(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED, keyCode)
+ sendAccessibilityEventForUnicodeCharacter(
+ AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED,
+ keyCode,
+ )
}
if (mCurrentKeyIndex != NOT_A_KEY && keys.size > mCurrentKeyIndex) {
val newKey = keys[mCurrentKeyIndex]
val code = newKey.code
- if (code == KEYCODE_SHIFT || code == KEYCODE_MODE_CHANGE || code == KEYCODE_DELETE || code == KEYCODE_ENTER || code == KEYCODE_SPACE) {
+ if (
+ code == KEYCODE_SHIFT ||
+ code == KEYCODE_MODE_CHANGE ||
+ code == KEYCODE_DELETE ||
+ code == KEYCODE_ENTER ||
+ code == KEYCODE_SPACE
+ ) {
newKey.pressed = true
}
@@ -814,12 +846,21 @@ class MyKeyboardView
}
val previewBackground = mPreviewText!!.background as LayerDrawable
- previewBackground.findDrawableByLayerId(R.id.button_background_shape).applyColorFilter(previewBackgroundColor)
- previewBackground.findDrawableByLayerId(R.id.button_background_stroke).applyColorFilter(context.getStrokeColor())
+ previewBackground
+ .findDrawableByLayerId(R.id.button_background_shape)
+ .applyColorFilter(previewBackgroundColor)
+
+ previewBackground
+ .findDrawableByLayerId(R.id.button_background_stroke)
+ .applyColorFilter(context.getStrokeColor())
+
mPreviewText!!.background = previewBackground
mPreviewText!!.setTextColor(mTextColor)
- mPreviewText!!.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED))
+ mPreviewText!!.measure(
+ MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
+ MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
+ )
val popupWidth = Math.max(mPreviewText!!.measuredWidth, key.width)
val popupHeight = mPreviewHeight
val lp = mPreviewText!!.layoutParams
@@ -889,7 +930,8 @@ class MyKeyboardView
}
/**
- * Requests a redraw of the entire keyboard. Calling [.invalidate] is not sufficient because the keyboard renders the keys to an off-screen buffer and
+ * Requests a redraw of the entire keyboard.
+ * Calling [.invalidate] is not sufficient because the keyboard renders the keys to an off-screen buffer and
* an invalidate() only draws the cached buffer.
*/
fun invalidateAllKeys() {
@@ -899,7 +941,8 @@ class MyKeyboardView
}
/**
- * Invalidates a key so that it will be redrawn on the next repaint. Use this method if only one key is changing it's content. Any changes that
+ * Invalidates a key so that it will be redrawn on the next repaint.
+ * Use this method if only one key is changing it's content. Any changes that
* affect the position or size of the key may not be honored.
* @param keyIndex the index of the key in the attached [MyKeyboard].
*/
@@ -945,10 +988,12 @@ class MyKeyboardView
}
/**
- * Called when a key is long pressed. By default this will open any popup keyboard associated with this key through the attributes
+ * Called when a key is long pressed.
+ * By default this will open any popup keyboard associated with this key through the attributes
* popupLayout and popupCharacters.
* @param popupKey the key that was long pressed
- * @return true if the long press is handled, false otherwise. Subclasses should call the method on the base class if the subclass doesn't wish to
+ * @return true if the long press is handled, false otherwise.
+ * Subclasses should call the method on the base class if the subclass doesn't wish to
* handle the call.
*/
private fun onLongPress(
@@ -961,7 +1006,10 @@ class MyKeyboardView
if (mMiniKeyboardContainer == null) {
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
mMiniKeyboardContainer = inflater.inflate(mPopupLayout, null)
- mMiniKeyboard = mMiniKeyboardContainer!!.findViewById(R.id.mini_keyboard_view) as MyKeyboardView
+ mMiniKeyboard =
+ mMiniKeyboardContainer!!
+ .findViewById(R.id.mini_keyboard_view)
+ as MyKeyboardView
mMiniKeyboard!!.mOnKeyboardActionListener =
object : OnKeyboardActionListener {
@@ -990,7 +1038,9 @@ class MyKeyboardView
mOnKeyboardActionListener!!.onText(text)
}
- override fun hasTextBeforeCursor(): Boolean = mOnKeyboardActionListener!!.hasTextBeforeCursor()
+ override fun hasTextBeforeCursor(): Boolean =
+ mOnKeyboardActionListener!!
+ .hasTextBeforeCursor()
override fun commitPeriodAfterSpace() {
mOnKeyboardActionListener!!.commitPeriodAfterSpace()
@@ -1012,14 +1062,19 @@ class MyKeyboardView
)
mMiniKeyboardCache[popupKey] = mMiniKeyboardContainer
} else {
- mMiniKeyboard = mMiniKeyboardContainer!!.findViewById(R.id.mini_keyboard_view) as MyKeyboardView
+ mMiniKeyboard =
+ mMiniKeyboardContainer!!
+ .findViewById(R.id.mini_keyboard_view) as MyKeyboardView
}
getLocationInWindow(mCoordinates)
mPopupX = popupKey.x
mPopupY = popupKey.y
- val widthToUse = mMiniKeyboardContainer!!.measuredWidth - (popupKey.popupCharacters!!.length / 2) * popupKey.width
+ val widthToUse =
+ mMiniKeyboardContainer!!.measuredWidth -
+ (popupKey.popupCharacters!!.length / 2) *
+ popupKey.width
mPopupX = mPopupX + popupKey.width - widthToUse
mPopupY -= mMiniKeyboardContainer!!.measuredHeight
val x = mPopupX + mCoordinates[0]
@@ -1027,7 +1082,8 @@ class MyKeyboardView
val xOffset = Math.max(0, x)
mMiniKeyboard!!.setPopupOffset(xOffset, y)
- // make sure we highlight the proper key right after long pressing it, before any ACTION_MOVE event occurs
+ // make sure we highlight the proper key right after long pressing it,
+ // before any ACTION_MOVE event occurs
val miniKeyboardX =
if (xOffset + mMiniKeyboard!!.measuredWidth <= measuredWidth) {
xOffset
@@ -1164,7 +1220,8 @@ class MyKeyboardView
when (action) {
MotionEvent.ACTION_POINTER_DOWN -> {
- // if the user presses a key while still holding down the previous, type in both chars and ignore the later gestures
+ // if the user presses a key while still holding down the previous,
+ // type in both chars and ignore the later gestures
// can happen at fast typing, easier to reproduce by increasing LONGPRESS_TIMEOUT
ignoreTouches = true
mHandler!!.removeMessages(MSG_LONGPRESS)
diff --git a/app/src/main/res/drawable/rv_divider.xml b/app/src/main/res/drawable/rv_divider.xml
index 68649987..23bbfd39 100644
--- a/app/src/main/res/drawable/rv_divider.xml
+++ b/app/src/main/res/drawable/rv_divider.xml
@@ -1,7 +1,10 @@
-
-
-
-
-
-
+
+
+
diff --git a/app/src/main/res/layout/fragment_main.xml b/app/src/main/res/layout/fragment_main.xml
index e4cd4ec2..430cba45 100644
--- a/app/src/main/res/layout/fragment_main.xml
+++ b/app/src/main/res/layout/fragment_main.xml
@@ -94,10 +94,11 @@
android:textSize="16sp" />