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" />