Merge pull request #214 from esensar/feature/199-autofill-integration

Add autofill integration to the keyboard
This commit is contained in:
Tibor Kaputa 2023-07-07 18:57:23 +02:00 committed by GitHub
commit a143e100db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 299 additions and 33 deletions

View File

@ -67,6 +67,7 @@ android {
dependencies {
implementation 'com.github.SimpleMobileTools:Simple-Commons:d6cddfa7d8'
implementation 'androidx.emoji2:emoji2-bundled:1.2.0'
implementation 'androidx.autofill:autofill:1.1.0'
kapt 'androidx.room:room-compiler:2.5.1'
implementation 'androidx.room:room-runtime:2.5.1'

View File

@ -33,7 +33,7 @@ class ManageClipboardItemsActivity : SimpleActivity(), RefreshRecyclerViewListen
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_manage_clipboard_items)
setupOptionsMenu()
updateTextColors(clipboard_items_holder)
updateTextColors(suggestions_items_holder)
updateClips()
updateMaterialActivityViews(clipboard_coordinator, clipboard_items_list, useTransparentNavigation = true, useTopSearchMenu = false)
@ -73,14 +73,17 @@ class ManageClipboardItemsActivity : SimpleActivity(), RefreshRecyclerViewListen
addOrEditClip()
true
}
R.id.export_clips -> {
exportClips()
true
}
R.id.import_clips -> {
importClips()
true
}
else -> false
}
}

View File

@ -1,17 +1,28 @@
package com.simplemobiletools.keyboard.services
import android.annotation.SuppressLint
import android.content.SharedPreferences
import android.graphics.drawable.Icon
import android.inputmethodservice.InputMethodService
import android.os.Build
import android.os.Bundle
import android.text.InputType.*
import android.text.TextUtils
import android.util.Size
import android.view.KeyEvent
import android.view.View
import android.view.inputmethod.CursorAnchorInfo
import android.view.inputmethod.EditorInfo
import android.view.ViewGroup
import android.view.inputmethod.*
import android.view.inputmethod.EditorInfo.IME_ACTION_NONE
import android.view.inputmethod.EditorInfo.IME_FLAG_NO_ENTER_ACTION
import android.view.inputmethod.EditorInfo.IME_MASK_ACTION
import android.view.inputmethod.ExtractedTextRequest
import android.widget.inline.InlinePresentationSpec
import androidx.annotation.RequiresApi
import androidx.autofill.inline.UiVersions
import androidx.autofill.inline.common.ImageViewStyle
import androidx.autofill.inline.common.TextViewStyle
import androidx.autofill.inline.common.ViewStyle
import androidx.autofill.inline.v1.InlineSuggestionUi
import com.simplemobiletools.commons.extensions.getSharedPrefs
import com.simplemobiletools.keyboard.R
import com.simplemobiletools.keyboard.extensions.config
@ -90,6 +101,37 @@ class SimpleKeyboardIME : InputMethodService(), OnKeyboardActionListener, Shared
keyboardView?.invalidateAllKeys()
}
@RequiresApi(Build.VERSION_CODES.R)
override fun onCreateInlineSuggestionsRequest(uiExtras: Bundle): InlineSuggestionsRequest {
val maxWidth = resources.getDimensionPixelSize(R.dimen.suggestion_max_width)
return InlineSuggestionsRequest.Builder(
listOf(
InlinePresentationSpec.Builder(
Size(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT),
Size(maxWidth, ViewGroup.LayoutParams.WRAP_CONTENT)
).setStyle(buildSuggestionTextStyle()).build()
)
).setMaxSuggestionCount(InlineSuggestionsRequest.SUGGESTION_COUNT_UNLIMITED)
.build()
}
@RequiresApi(Build.VERSION_CODES.R)
override fun onInlineSuggestionsResponse(response: InlineSuggestionsResponse): Boolean {
keyboardView?.clearClipboardViews()
response.inlineSuggestions.forEach {
it.inflate(this, Size(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT), this.mainExecutor) { view ->
// If inflation fails for whatever reason, passed view will be null
if (view != null) {
keyboardView?.addToClipboardViews(view, addToFront = it.info.isPinned)
}
}
}
return true
}
override fun onKey(code: Int) {
val inputConnection = currentInputConnection
if (keyboard == null || inputConnection == null) {
@ -201,6 +243,7 @@ class SimpleKeyboardIME : InputMethodService(), OnKeyboardActionListener, Shared
inputConnection.commitText(codeChar.toString(), 1)
}
}
else -> {
inputConnection.commitText(codeChar.toString(), 1)
if (originalText == null) {
@ -335,6 +378,48 @@ class SimpleKeyboardIME : InputMethodService(), OnKeyboardActionListener, Shared
}
}
@RequiresApi(Build.VERSION_CODES.R)
@SuppressLint("RestrictedApi", "UseCompatLoadingForDrawables")
private fun buildSuggestionTextStyle(): Bundle {
val stylesBuilder = UiVersions.newStylesBuilder()
val verticalPadding = resources.getDimensionPixelSize(R.dimen.small_margin)
val horizontalPadding = resources.getDimensionPixelSize(R.dimen.activity_margin)
val textSize = resources.getDimension(R.dimen.label_text_size) / resources.displayMetrics.scaledDensity
val chipStyle =
ViewStyle.Builder()
.setBackground(Icon.createWithResource(this, R.drawable.clipboard_background))
.setPadding(horizontalPadding, verticalPadding, horizontalPadding, verticalPadding)
.build()
val iconStyle = ImageViewStyle.Builder().build()
val style = InlineSuggestionUi.newStyleBuilder()
.setSingleIconChipStyle(chipStyle)
.setChipStyle(chipStyle)
.setStartIconStyle(iconStyle)
.setEndIconStyle(iconStyle)
.setSingleIconChipIconStyle(iconStyle)
.setTitleStyle(
TextViewStyle.Builder()
.setLayoutMargin(0, 0, horizontalPadding, 0)
.setTextColor(resources.getColor(R.color.default_text_color, theme))
.setTextSize(textSize)
.build()
)
.setSubtitleStyle(
TextViewStyle.Builder()
.setTextColor(resources.getColor(R.color.default_text_color, theme))
.setTextSize(textSize)
.build()
)
.build()
stylesBuilder.addStyle(style)
return stylesBuilder.build()
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
keyboardView?.setupKeyboard()
}

View File

@ -0,0 +1,82 @@
package com.simplemobiletools.keyboard.views
import android.content.Context
import android.graphics.Rect
import android.util.AttributeSet
import android.view.ViewTreeObserver.OnDrawListener
import android.widget.HorizontalScrollView
import android.widget.inline.InlineContentView
import androidx.annotation.AttrRes
import androidx.core.view.allViews
import com.simplemobiletools.commons.extensions.beInvisible
import com.simplemobiletools.commons.extensions.beVisible
import com.simplemobiletools.commons.helpers.isRPlus
/**
* [HorizontalScrollView] adapted for holding [InlineContentView] instances
* It can hold other views too, but it will ensure [InlineContentView] instances
* these are properly clipped and not drawn over rest of the window,
* but still remaining clickable
* (since setting [InlineContentView.setZOrderedOnTop] to false prevents clicking)
*/
class InlineContentViewHorizontalScrollView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null,
@AttrRes defStyleAttr: Int = 0
) : HorizontalScrollView(context, attrs, defStyleAttr), OnDrawListener {
override fun onAttachedToWindow() {
super.onAttachedToWindow()
viewTreeObserver.addOnDrawListener(this)
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
viewTreeObserver.removeOnDrawListener(this)
}
override fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int) {
super.onScrollChanged(l, t, oldl, oldt)
clipDescendantInlineContentViews()
}
override fun onDraw() {
clipDescendantInlineContentViews()
}
fun hideAllInlineContentViews() {
if (!isRPlus()) {
return
}
allViews.forEach {
if (it is InlineContentView) {
it.beInvisible()
}
}
}
fun showAllInlineContentViews() {
if (!isRPlus()) {
return
}
allViews.forEach {
if (it is InlineContentView) {
it.beVisible()
}
}
}
private fun clipDescendantInlineContentViews() {
// This is only needed for InlineContentViews which are not available before this version
if (!isRPlus()) {
return
}
allViews.forEach {
if (it is InlineContentView) {
val parentBounds = Rect(scrollX, scrollY, width + scrollX, height + scrollY)
offsetRectIntoDescendantCoords(it, parentBounds)
it.clipBounds = parentBounds
}
}
}
}

View File

@ -10,6 +10,7 @@ import android.content.Intent
import android.graphics.*
import android.graphics.Paint.Align
import android.graphics.drawable.*
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.os.Message
@ -18,11 +19,14 @@ import android.util.TypedValue
import android.view.*
import android.view.animation.AccelerateInterpolator
import android.view.inputmethod.EditorInfo
import android.widget.LinearLayout
import android.widget.PopupWindow
import android.widget.TextView
import android.widget.inline.InlineContentView
import androidx.annotation.RequiresApi
import androidx.core.animation.doOnEnd
import androidx.core.animation.doOnStart
import androidx.core.view.ViewCompat
import androidx.core.view.*
import androidx.emoji2.text.EmojiCompat
import androidx.emoji2.text.EmojiCompat.EMOJI_SUPPORTED
import com.simplemobiletools.commons.extensions.*
@ -308,6 +312,13 @@ class MyKeyboardView @JvmOverloads constructor(context: Context, attrs: Attribut
clearClipboardContent()
toggleClipboardVisibility(false)
}
suggestions_holder.addOnLayoutChangeListener(object : OnLayoutChangeListener {
override fun onLayoutChange(v: View?, left: Int, top: Int, right: Int, bottom: Int, oldLeft: Int, oldTop: Int, oldRight: Int, oldBottom: Int) {
updateSuggestionsToolbarLayout()
suggestions_holder.removeOnLayoutChangeListener(this)
}
})
}
val clipboardManager = (context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager)
@ -746,8 +757,8 @@ class MyKeyboardView @JvmOverloads constructor(context: Context, attrs: Attribut
private fun hideClipboardViews() {
mToolbarHolder?.apply {
clipboard_value_holder?.beGone()
clipboard_value_holder?.alpha = 0f
clipboard_value?.beGone()
clipboard_value?.alpha = 0f
clipboard_clear?.beGone()
clipboard_clear?.alpha = 0f
}
@ -764,10 +775,10 @@ class MyKeyboardView @JvmOverloads constructor(context: Context, attrs: Attribut
}
private fun toggleClipboardVisibility(show: Boolean) {
if ((show && mToolbarHolder?.clipboard_value_holder!!.alpha == 0f) || (!show && mToolbarHolder?.clipboard_value_holder!!.alpha == 1f)) {
if ((show && mToolbarHolder?.clipboard_value!!.alpha == 0f) || (!show && mToolbarHolder?.clipboard_value!!.alpha == 1f)) {
val newAlpha = if (show) 1f else 0f
val animations = ArrayList<ObjectAnimator>()
val clipboardValueAnimation = ObjectAnimator.ofFloat(mToolbarHolder!!.clipboard_value_holder!!, "alpha", newAlpha)
val clipboardValueAnimation = ObjectAnimator.ofFloat(mToolbarHolder!!.clipboard_value!!, "alpha", newAlpha)
animations.add(clipboardValueAnimation)
val clipboardClearAnimation = ObjectAnimator.ofFloat(mToolbarHolder!!.clipboard_clear!!, "alpha", newAlpha)
@ -779,13 +790,13 @@ class MyKeyboardView @JvmOverloads constructor(context: Context, attrs: Attribut
animSet.interpolator = AccelerateInterpolator()
animSet.doOnStart {
if (show) {
mToolbarHolder?.clipboard_value_holder?.beVisible()
mToolbarHolder?.clipboard_value?.beVisible()
mToolbarHolder?.clipboard_clear?.beVisible()
}
}
animSet.doOnEnd {
if (!show) {
mToolbarHolder?.clipboard_value_holder?.beGone()
mToolbarHolder?.clipboard_value?.beGone()
mToolbarHolder?.clipboard_clear?.beGone()
}
}
@ -1381,10 +1392,12 @@ class MyKeyboardView @JvmOverloads constructor(context: Context, attrs: Attribut
fun closeClipboardManager() {
mClipboardManagerHolder?.clipboard_manager_holder?.beGone()
mToolbarHolder?.suggestions_holder?.showAllInlineContentViews()
}
private fun openClipboardManager() {
mClipboardManagerHolder!!.clipboard_manager_holder.beVisible()
mToolbarHolder?.suggestions_holder?.hideAllInlineContentViews()
setupStoredClips()
}
@ -1614,4 +1627,46 @@ class MyKeyboardView @JvmOverloads constructor(context: Context, attrs: Attribut
}
return keyColor
}
@RequiresApi(Build.VERSION_CODES.R)
fun addToClipboardViews(it: InlineContentView, addToFront: Boolean = false) {
if (mToolbarHolder?.autofill_suggestions_holder != null) {
val newLayoutParams = LinearLayout.LayoutParams(it.layoutParams)
newLayoutParams.updateMarginsRelative(start = resources.getDimensionPixelSize(R.dimen.normal_margin))
it.layoutParams = newLayoutParams
if (addToFront) {
mToolbarHolder?.autofill_suggestions_holder?.addView(it, 0)
} else {
mToolbarHolder?.autofill_suggestions_holder?.addView(it)
}
updateSuggestionsToolbarLayout()
}
}
@RequiresApi(Build.VERSION_CODES.R)
fun clearClipboardViews() {
mToolbarHolder?.autofill_suggestions_holder?.removeAllViews()
updateSuggestionsToolbarLayout()
}
private fun updateSuggestionsToolbarLayout() {
mToolbarHolder?.apply {
if (hasInlineViews()) {
// make room on suggestion toolbar for inline views
suggestions_items_holder?.gravity = Gravity.NO_GRAVITY
clipboard_value?.maxWidth = resources.getDimensionPixelSize(R.dimen.suggestion_max_width)
} else {
// restore original clipboard toolbar appearance
suggestions_items_holder?.gravity = Gravity.CENTER_HORIZONTAL
suggestions_holder?.measuredWidth?.also { maxWidth ->
clipboard_value?.maxWidth = maxWidth
}
}
}
}
/**
* Returns true if there are [InlineContentView]s in [autofill_suggestions_holder]
*/
private fun hasInlineViews() = (mToolbarHolder?.autofill_suggestions_holder?.childCount ?: 0) > 0
}

View File

@ -49,13 +49,13 @@
<com.simplemobiletools.commons.views.MyEditText
android:id="@+id/text_edittext"
android:inputType="textCapSentences"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/activity_margin"
android:layout_marginTop="@dimen/activity_margin"
android:layout_marginEnd="@dimen/activity_margin"
android:layout_marginBottom="@dimen/activity_margin" />
android:layout_marginBottom="@dimen/activity_margin"
android:inputType="textCapSentences" />
<com.simplemobiletools.commons.views.MyEditText
android:id="@+id/text_editphone"
@ -67,6 +67,26 @@
android:layout_marginBottom="@dimen/activity_margin"
android:inputType="phone" />
<com.simplemobiletools.commons.views.MyEditText
android:id="@+id/text_editemail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/activity_margin"
android:layout_marginTop="@dimen/activity_margin"
android:layout_marginEnd="@dimen/activity_margin"
android:layout_marginBottom="@dimen/activity_margin"
android:inputType="textEmailAddress" />
<com.simplemobiletools.commons.views.MyEditText
android:id="@+id/text_editpassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/activity_margin"
android:layout_marginTop="@dimen/activity_margin"
android:layout_marginEnd="@dimen/activity_margin"
android:layout_marginBottom="@dimen/activity_margin"
android:inputType="textPassword" />
</LinearLayout>
</RelativeLayout>
</androidx.core.widget.NestedScrollView>

View File

@ -23,7 +23,7 @@
android:scrollbars="none">
<RelativeLayout
android:id="@+id/clipboard_items_holder"
android:id="@+id/suggestions_items_holder"
android:layout_width="match_parent"
android:layout_height="match_parent">

View File

@ -27,35 +27,51 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<RelativeLayout
android:id="@+id/clipboard_value_holder"
<com.simplemobiletools.keyboard.views.InlineContentViewHorizontalScrollView
android:id="@+id/suggestions_holder"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/medium_margin"
android:layout_marginEnd="@dimen/normal_margin"
android:fillViewport="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/pinned_clipboard_items"
app:layout_constraintStart_toEndOf="@+id/clipboard_clear"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/clipboard_value"
<LinearLayout
android:id="@+id/suggestions_items_holder"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:autoLink="none"
android:background="@drawable/clipboard_background"
android:ellipsize="end"
android:gravity="center"
android:lines="1"
android:paddingStart="@dimen/activity_margin"
android:paddingTop="@dimen/small_margin"
android:paddingEnd="@dimen/activity_margin"
android:paddingBottom="@dimen/small_margin"
android:textSize="@dimen/label_text_size"
tools:text="Clipboard content" />
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="horizontal">
</RelativeLayout>
<TextView
android:id="@+id/clipboard_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:autoLink="none"
android:background="@drawable/clipboard_background"
android:ellipsize="end"
android:gravity="center"
android:lines="1"
android:paddingStart="@dimen/activity_margin"
android:paddingTop="@dimen/small_margin"
android:paddingEnd="@dimen/activity_margin"
android:paddingBottom="@dimen/small_margin"
android:textSize="@dimen/label_text_size"
tools:text="Clipboard content" />
<LinearLayout
android:id="@+id/autofill_suggestions_holder"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal" />
</LinearLayout>
</com.simplemobiletools.keyboard.views.InlineContentViewHorizontalScrollView>
<ImageView
android:id="@+id/pinned_clipboard_items"

View File

@ -11,6 +11,7 @@
<dimen name="emoji_item_size">46dp</dimen>
<dimen name="emoji_top_bar_elevation">4dp</dimen>
<dimen name="emoji_palette_btn_size">42dp</dimen>
<dimen name="suggestion_max_width">200dp</dimen>
<dimen name="keyboard_text_size">22sp</dimen>
<dimen name="preview_text_size">26sp</dimen>

View File

@ -1,7 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<input-method xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:icon="@mipmap/ic_launcher"
android:settingsActivity="com.simplemobiletools.keyboard.activities.SettingsActivity">
android:settingsActivity="com.simplemobiletools.keyboard.activities.SettingsActivity"
android:supportsInlineSuggestions="true"
tools:targetApi="r">
<subtype android:imeSubtypeMode="Keyboard" />