-
-
Notifications
You must be signed in to change notification settings - Fork 307
feat(clipboard): add image pasting #2312
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,6 +4,7 @@ package helium314.keyboard.latin | |
|
|
||
| import android.content.ClipboardManager | ||
| import android.content.Context | ||
| import android.net.Uri | ||
| import android.text.InputType | ||
| import android.text.TextUtils | ||
| import android.view.LayoutInflater | ||
|
|
@@ -101,6 +102,12 @@ class ClipboardHistoryManager( | |
| return clipData.getItemAt(0)?.coerceToText(latinIME) ?: "" | ||
| } | ||
|
|
||
| fun retrieveClipboardUri(): Uri? { | ||
| val clipData = clipboardManager.primaryClip ?: return null | ||
| if (clipData.itemCount == 0) return null | ||
| return clipData.getItemAt(0)?.uri | ||
| } | ||
|
|
||
| private fun isClipSensitive(inputType: Int): Boolean { | ||
| ClipboardManagerCompat.getClipSensitivity(clipboardManager.primaryClip?.description)?.let { return it } | ||
| return InputTypeUtils.isPasswordInputType(inputType) | ||
|
|
@@ -116,16 +123,30 @@ class ClipboardHistoryManager( | |
| if (dontShowCurrentSuggestion) return null | ||
| if (parent == null) return null | ||
| val clipData = clipboardManager.primaryClip ?: return null | ||
| if (clipData.itemCount == 0 || clipData.description?.hasMimeType("text/*") == false) return null | ||
| if (clipData.itemCount == 0) return null | ||
| val description = clipData.description | ||
| val hasText = description?.hasMimeType("text/*") == true | ||
| val hasImage = description?.hasMimeType("image/*") == true | ||
| if (!hasText && !hasImage) return null | ||
| val clipItem = clipData.getItemAt(0) ?: return null | ||
| val timeStamp = ClipboardManagerCompat.getClipTimestamp(clipData) | ||
| if (System.currentTimeMillis() - timeStamp > RECENT_TIME_MILLIS) return null | ||
| val content = clipItem.coerceToText(latinIME) | ||
| if (TextUtils.isEmpty(content)) return null | ||
| val inputType = editorInfo?.inputType ?: InputType.TYPE_NULL | ||
| if (InputTypeUtils.isNumberInputType(inputType) && !content.isValidNumber()) return null | ||
|
|
||
| // create the view | ||
| val imageUri = if (hasImage) clipItem.uri else null | ||
|
|
||
| if (imageUri == null) { | ||
| val content = clipItem.coerceToText(latinIME) | ||
| if (TextUtils.isEmpty(content)) return null | ||
| val inputType = editorInfo?.inputType ?: InputType.TYPE_NULL | ||
| if (InputTypeUtils.isNumberInputType(inputType) && !content.isValidNumber()) return null | ||
| return createTextSuggestionView(content, editorInfo, parent) | ||
| } | ||
|
|
||
| return createImageSuggestionView(imageUri, parent) | ||
| } | ||
|
|
||
| private fun createTextSuggestionView(content: CharSequence, editorInfo: EditorInfo?, parent: ViewGroup): View { | ||
| val inputType = editorInfo?.inputType ?: InputType.TYPE_NULL | ||
| val binding = ClipboardSuggestionBinding.inflate(LayoutInflater.from(latinIME), parent, false) | ||
| val textView = binding.clipboardSuggestionText | ||
| latinIME.mSettings.getCustomTypeface()?.let { textView.typeface = it } | ||
|
|
@@ -139,18 +160,38 @@ class ClipboardHistoryManager( | |
| AudioAndHapticFeedbackManager.getInstance().performHapticAndAudioFeedback(KeyCode.NOT_SPECIFIED, it, HapticEvent.KEY_PRESS) | ||
| binding.root.isGone = true | ||
| } | ||
| setupCloseButtonAndColors(binding, clipIcon) | ||
| clipboardSuggestionView = binding.root | ||
| return binding.root | ||
| } | ||
|
|
||
| private fun createImageSuggestionView(uri: Uri, parent: ViewGroup): View { | ||
| val binding = ClipboardSuggestionBinding.inflate(LayoutInflater.from(latinIME), parent, false) | ||
| binding.clipboardSuggestionText.isGone = true | ||
| val imageView = binding.clipboardSuggestionImage | ||
| imageView.visibility = View.VISIBLE | ||
|
Comment on lines
+170
to
+172
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd prefer use of the extensions ( |
||
| imageView.setImageURI(uri) | ||
| imageView.setOnClickListener { | ||
| dontShowCurrentSuggestion = true | ||
| latinIME.onUriInput(uri) | ||
| AudioAndHapticFeedbackManager.getInstance().performHapticAndAudioFeedback(KeyCode.NOT_SPECIFIED, it, HapticEvent.KEY_PRESS) | ||
| binding.root.isGone = true | ||
| } | ||
| setupCloseButtonAndColors(binding) | ||
| clipboardSuggestionView = binding.root | ||
| return binding.root | ||
| } | ||
|
|
||
| private fun setupCloseButtonAndColors(binding: ClipboardSuggestionBinding, clipIcon: android.graphics.drawable.Drawable? = null) { | ||
| val closeButton = binding.clipboardSuggestionClose | ||
| closeButton.setImageDrawable(latinIME.mKeyboardSwitcher.keyboard.mIconsSet.getIconDrawable(ToolbarKey.CLOSE_HISTORY.name.lowercase())) | ||
| closeButton.setOnClickListener { removeClipboardSuggestion() } | ||
|
|
||
| val colors = latinIME.mSettings.current.mColors | ||
| textView.setTextColor(colors.get(ColorType.KEY_TEXT)) | ||
| binding.clipboardSuggestionText.setTextColor(colors.get(ColorType.KEY_TEXT)) | ||
| clipIcon?.let { colors.setColor(it, ColorType.KEY_ICON) } | ||
| colors.setColor(closeButton, ColorType.REMOVE_SUGGESTION_ICON) | ||
| colors.setBackground(binding.root, ColorType.CLIPBOARD_SUGGESTION_BACKGROUND) | ||
|
|
||
| clipboardSuggestionView = binding.root | ||
| return clipboardSuggestionView | ||
| } | ||
|
|
||
| private fun removeClipboardSuggestion() { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,7 @@ | |
| import android.annotation.SuppressLint; | ||
| import android.app.AlertDialog; | ||
| import android.content.BroadcastReceiver; | ||
| import android.content.ContentResolver; | ||
| import android.content.Context; | ||
| import android.content.Intent; | ||
| import android.content.IntentFilter; | ||
|
|
@@ -17,6 +18,7 @@ | |
| import android.graphics.Color; | ||
| import android.inputmethodservice.InputMethodService; | ||
| import android.media.AudioManager; | ||
| import android.net.Uri; | ||
| import android.os.Build; | ||
| import android.os.Bundle; | ||
| import android.os.Debug; | ||
|
|
@@ -1389,6 +1391,25 @@ public void onTextInput(final String rawText) { | |
| mKeyboardSwitcher.onEvent(event, getCurrentAutoCapsState(), getCurrentRecapitalizeState()); | ||
| } | ||
|
|
||
| public void onUriInput(final Uri uri) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is the handling done in Also, |
||
| final EditorInfo editorInfo = getCurrentInputEditorInfo(); | ||
| if (editorInfo == null) return; | ||
|
|
||
| String mimeType = getContentResolver().getType(uri); | ||
| if (mimeType == null) { | ||
| // ContentResolver may fail for some providers (e.g. Samsung clipboard). | ||
| // Fall back to the MIME type declared in the system clipboard's ClipDescription. | ||
| final android.content.ClipData clipData = ((android.content.ClipboardManager) | ||
| getSystemService(Context.CLIPBOARD_SERVICE)).getPrimaryClip(); | ||
| if (clipData != null && clipData.getDescription().getMimeTypeCount() > 0) { | ||
| mimeType = clipData.getDescription().getMimeType(0); | ||
| } | ||
| } | ||
| if (mimeType == null) mimeType = "application/octet-stream"; | ||
|
|
||
| mInputLogic.mConnection.commitContent(uri, mimeType, editorInfo); | ||
| } | ||
|
|
||
| public void onStartBatchInput() { | ||
| mInputLogic.onStartBatchInput(mSettings.getCurrent(), mKeyboardSwitcher, mHandler); | ||
| mGestureConsumer.onGestureStarted(mRichImm.getCurrentSubtypeLocale(), mKeyboardSwitcher.getKeyboard()); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,6 +10,7 @@ | |
| import android.content.ClipboardManager; | ||
| import android.content.Context; | ||
| import android.inputmethodservice.InputMethodService; | ||
| import android.net.Uri; | ||
| import android.os.Build; | ||
| import android.os.Bundle; | ||
| import android.os.SystemClock; | ||
|
|
@@ -24,13 +25,16 @@ | |
| import android.view.KeyEvent; | ||
| import android.view.inputmethod.CompletionInfo; | ||
| import android.view.inputmethod.CorrectionInfo; | ||
| import android.view.inputmethod.EditorInfo; | ||
| import android.view.inputmethod.ExtractedText; | ||
| import android.view.inputmethod.ExtractedTextRequest; | ||
| import android.view.inputmethod.InputConnection; | ||
| import android.view.inputmethod.InputMethodManager; | ||
|
|
||
| import androidx.annotation.NonNull; | ||
| import androidx.annotation.Nullable; | ||
| import androidx.core.view.inputmethod.InputConnectionCompat; | ||
| import androidx.core.view.inputmethod.InputContentInfoCompat; | ||
|
|
||
| import helium314.keyboard.latin.common.Constants; | ||
| import helium314.keyboard.latin.common.StringUtils; | ||
|
|
@@ -1176,4 +1180,17 @@ public boolean requestCursorUpdates(final boolean enableMonitor, final boolean r | |
| | (requestImmediateCallback ? InputConnection.CURSOR_UPDATE_IMMEDIATE : 0); | ||
| return mIC.requestCursorUpdates(cursorUpdateMode); | ||
| } | ||
|
|
||
| public boolean commitContent(@NonNull final Uri uri, @NonNull final String mimeType, | ||
| @NonNull final EditorInfo editorInfo) { | ||
| mIC = mParent.getCurrentInputConnection(); | ||
| if (!isConnected()) { | ||
| return false; | ||
| } | ||
| final InputContentInfoCompat contentInfo = new InputContentInfoCompat(uri, | ||
| new android.content.ClipDescription("clipboard image", new String[]{mimeType}), | ||
| null); | ||
| return InputConnectionCompat.commitContent(mIC, editorInfo, contentInfo, | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why a return value when it's not used? Is this related to the unused toast message? |
||
| InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION, null); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -651,6 +651,11 @@ private void handleConsumedEvent(final Event event, final InputTransaction input | |
| * | ||
| */ | ||
| private void handleClipboardPaste() { | ||
| final android.net.Uri clipUri = mLatinIME.getClipboardHistoryManager().retrieveClipboardUri(); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why |
||
| if (clipUri != null) { | ||
| mLatinIME.onUriInput(clipUri); | ||
| return; | ||
| } | ||
| final String clipboardContent = mLatinIME.getClipboardHistoryManager().retrieveClipboardContent().toString(); | ||
| if (!clipboardContent.isEmpty()) { | ||
| mLatinIME.onTextInput(clipboardContent); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This appears to be useful, but is not used at all. (I assume the unused
toast_msg_unsupported_uriis related)