From 1cbbf4983c06386b2cfea54d61ee18b752cd1793 Mon Sep 17 00:00:00 2001 From: "pdroidandroid@gmail.com" Date: Thu, 13 Jan 2022 13:45:48 +0100 Subject: [PATCH] Implemented clipboard history feature --- app/build.gradle | 1 + .../compat/ClipboardManagerCompat.java | 19 ++ .../inputmethod/keyboard/KeyboardId.java | 2 + .../keyboard/KeyboardSwitcher.java | 54 ++++- .../keyboard/clipboard/ClipboardAdapter.kt | 93 +++++++ .../clipboard/ClipboardHistoryRecyclerView.kt | 78 ++++++ .../clipboard/ClipboardHistoryView.kt | 226 ++++++++++++++++++ .../clipboard/ClipboardLayoutParams.kt | 68 ++++++ .../keyboard/clipboard/OnKeyEventListener.kt | 9 + .../keyboard/internal/KeyboardCodesSet.java | 4 + .../keyboard/internal/KeyboardIconsSet.java | 8 +- .../keyboard/internal/KeyboardState.java | 83 ++++--- .../keyboard/internal/KeyboardTextsTable.java | 4 + .../latin/ClipboardHistoryEntry.kt | 13 + .../latin/ClipboardHistoryManager.kt | 166 +++++++++++++ .../openboard/inputmethod/latin/LatinIME.java | 21 +- .../inputmethod/latin/common/Constants.java | 12 +- .../latin/inputlogic/InputLogic.java | 8 + .../suggestions/SuggestionStripView.java | 11 +- .../inputmethod/latin/utils/JsonUtils.java | 64 +++++ .../ic_clipboard_pin_holo_dark.png | Bin 0 -> 566 bytes .../ic_clipboard_pin_lxx_dark.png | Bin 0 -> 658 bytes .../ic_clipboard_pin_lxx_light.png | Bin 0 -> 658 bytes ...sym_keyboard_clear_clipboard_holo_dark.png | Bin 0 -> 1124 bytes .../sym_keyboard_clear_clipboard_lxx_dark.png | Bin 0 -> 978 bytes ...sym_keyboard_clear_clipboard_lxx_light.png | Bin 0 -> 1104 bytes .../sym_keyboard_clipboard_holo_dark.png | Bin 0 -> 751 bytes .../sym_keyboard_clipboard_lxx_dark.png | Bin 0 -> 611 bytes .../sym_keyboard_clipboard_lxx_light.png | Bin 0 -> 728 bytes .../ic_clipboard_pin_holo_dark.png | Bin 0 -> 414 bytes ...sym_keyboard_clear_clipboard_holo_dark.png | Bin 0 -> 777 bytes .../sym_keyboard_clipboard_holo_dark.png | Bin 0 -> 522 bytes .../ic_clipboard_pin_holo_dark.png | Bin 0 -> 573 bytes .../ic_clipboard_pin_lxx_dark.png | Bin 0 -> 955 bytes .../ic_clipboard_pin_lxx_light.png | Bin 0 -> 960 bytes ...sym_keyboard_clear_clipboard_holo_dark.png | Bin 0 -> 1276 bytes .../sym_keyboard_clear_clipboard_lxx_dark.png | Bin 0 -> 1283 bytes ...sym_keyboard_clear_clipboard_lxx_light.png | Bin 0 -> 1436 bytes .../sym_keyboard_clipboard_holo_dark.png | Bin 0 -> 936 bytes .../sym_keyboard_clipboard_lxx_dark.png | Bin 0 -> 851 bytes .../sym_keyboard_clipboard_lxx_light.png | Bin 0 -> 975 bytes .../ic_clipboard_pin_holo_dark.png | Bin 0 -> 835 bytes .../ic_clipboard_pin_lxx_dark.png | Bin 0 -> 1092 bytes .../ic_clipboard_pin_lxx_light.png | Bin 0 -> 1098 bytes ...sym_keyboard_clear_clipboard_holo_dark.png | Bin 0 -> 1910 bytes .../sym_keyboard_clear_clipboard_lxx_dark.png | Bin 0 -> 1800 bytes ...sym_keyboard_clear_clipboard_lxx_light.png | Bin 0 -> 1772 bytes .../sym_keyboard_clipboard_holo_dark.png | Bin 0 -> 1365 bytes .../sym_keyboard_clipboard_lxx_dark.png | Bin 0 -> 1084 bytes .../sym_keyboard_clipboard_lxx_light.png | Bin 0 -> 1105 bytes .../ic_clipboard_pin_holo_dark.png | Bin 0 -> 797 bytes .../ic_clipboard_pin_lxx_dark.png | Bin 0 -> 1705 bytes .../ic_clipboard_pin_lxx_light.png | Bin 0 -> 1579 bytes ...sym_keyboard_clear_clipboard_holo_dark.png | Bin 0 -> 2080 bytes .../sym_keyboard_clear_clipboard_lxx_dark.png | Bin 0 -> 2156 bytes ...sym_keyboard_clear_clipboard_lxx_light.png | Bin 0 -> 2378 bytes .../sym_keyboard_clipboard_holo_dark.png | Bin 0 -> 1718 bytes .../sym_keyboard_clipboard_lxx_dark.png | Bin 0 -> 1544 bytes .../sym_keyboard_clipboard_lxx_light.png | Bin 0 -> 1685 bytes .../main/res/layout/clipboard_entry_key.xml | 30 +++ .../res/layout/clipboard_history_view.xml | 64 +++++ app/src/main/res/layout/input_view.xml | 3 + app/src/main/res/layout/suggestions_strip.xml | 1 - app/src/main/res/values-land/config.xml | 3 + .../main/res/values-sw600dp-land/config.xml | 3 + app/src/main/res/values-sw600dp/config.xml | 3 + app/src/main/res/values/attrs.xml | 11 +- app/src/main/res/values/config-common.xml | 4 + app/src/main/res/values/config.xml | 3 + .../main/res/values/keyboard-icons-holo.xml | 4 +- .../values/keyboard-icons-lxx-dark-parent.xml | 4 +- .../keyboard-icons-lxx-light-parent.xml | 4 +- app/src/main/res/values/themes-common.xml | 1 + app/src/main/res/values/themes-ics.xml | 8 + app/src/main/res/values/themes-klp.xml | 6 + .../res/values/themes-lxx-dark-amoled.xml | 9 + .../res/values/themes-lxx-dark-border.xml | 9 + app/src/main/res/values/themes-lxx-dark.xml | 8 + .../res/values/themes-lxx-light-border.xml | 9 + app/src/main/res/values/themes-lxx-light.xml | 8 + .../res/xml/key_styles_navigate_more_keys.xml | 28 +-- app/src/main/res/xml/key_styles_settings.xml | 33 ++- 82 files changed, 1128 insertions(+), 72 deletions(-) create mode 100644 app/src/main/java/org/dslul/openboard/inputmethod/compat/ClipboardManagerCompat.java create mode 100644 app/src/main/java/org/dslul/openboard/inputmethod/keyboard/clipboard/ClipboardAdapter.kt create mode 100644 app/src/main/java/org/dslul/openboard/inputmethod/keyboard/clipboard/ClipboardHistoryRecyclerView.kt create mode 100644 app/src/main/java/org/dslul/openboard/inputmethod/keyboard/clipboard/ClipboardHistoryView.kt create mode 100644 app/src/main/java/org/dslul/openboard/inputmethod/keyboard/clipboard/ClipboardLayoutParams.kt create mode 100644 app/src/main/java/org/dslul/openboard/inputmethod/keyboard/clipboard/OnKeyEventListener.kt create mode 100644 app/src/main/java/org/dslul/openboard/inputmethod/latin/ClipboardHistoryEntry.kt create mode 100644 app/src/main/java/org/dslul/openboard/inputmethod/latin/ClipboardHistoryManager.kt create mode 100644 app/src/main/res/drawable-hdpi/ic_clipboard_pin_holo_dark.png create mode 100644 app/src/main/res/drawable-hdpi/ic_clipboard_pin_lxx_dark.png create mode 100644 app/src/main/res/drawable-hdpi/ic_clipboard_pin_lxx_light.png create mode 100644 app/src/main/res/drawable-hdpi/sym_keyboard_clear_clipboard_holo_dark.png create mode 100644 app/src/main/res/drawable-hdpi/sym_keyboard_clear_clipboard_lxx_dark.png create mode 100644 app/src/main/res/drawable-hdpi/sym_keyboard_clear_clipboard_lxx_light.png create mode 100644 app/src/main/res/drawable-hdpi/sym_keyboard_clipboard_holo_dark.png create mode 100644 app/src/main/res/drawable-hdpi/sym_keyboard_clipboard_lxx_dark.png create mode 100644 app/src/main/res/drawable-hdpi/sym_keyboard_clipboard_lxx_light.png create mode 100644 app/src/main/res/drawable-mdpi/ic_clipboard_pin_holo_dark.png create mode 100644 app/src/main/res/drawable-mdpi/sym_keyboard_clear_clipboard_holo_dark.png create mode 100644 app/src/main/res/drawable-mdpi/sym_keyboard_clipboard_holo_dark.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_clipboard_pin_holo_dark.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_clipboard_pin_lxx_dark.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_clipboard_pin_lxx_light.png create mode 100644 app/src/main/res/drawable-xhdpi/sym_keyboard_clear_clipboard_holo_dark.png create mode 100644 app/src/main/res/drawable-xhdpi/sym_keyboard_clear_clipboard_lxx_dark.png create mode 100644 app/src/main/res/drawable-xhdpi/sym_keyboard_clear_clipboard_lxx_light.png create mode 100644 app/src/main/res/drawable-xhdpi/sym_keyboard_clipboard_holo_dark.png create mode 100644 app/src/main/res/drawable-xhdpi/sym_keyboard_clipboard_lxx_dark.png create mode 100644 app/src/main/res/drawable-xhdpi/sym_keyboard_clipboard_lxx_light.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_clipboard_pin_holo_dark.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_clipboard_pin_lxx_dark.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_clipboard_pin_lxx_light.png create mode 100644 app/src/main/res/drawable-xxhdpi/sym_keyboard_clear_clipboard_holo_dark.png create mode 100644 app/src/main/res/drawable-xxhdpi/sym_keyboard_clear_clipboard_lxx_dark.png create mode 100644 app/src/main/res/drawable-xxhdpi/sym_keyboard_clear_clipboard_lxx_light.png create mode 100644 app/src/main/res/drawable-xxhdpi/sym_keyboard_clipboard_holo_dark.png create mode 100644 app/src/main/res/drawable-xxhdpi/sym_keyboard_clipboard_lxx_dark.png create mode 100644 app/src/main/res/drawable-xxhdpi/sym_keyboard_clipboard_lxx_light.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_clipboard_pin_holo_dark.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_clipboard_pin_lxx_dark.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_clipboard_pin_lxx_light.png create mode 100644 app/src/main/res/drawable-xxxhdpi/sym_keyboard_clear_clipboard_holo_dark.png create mode 100644 app/src/main/res/drawable-xxxhdpi/sym_keyboard_clear_clipboard_lxx_dark.png create mode 100644 app/src/main/res/drawable-xxxhdpi/sym_keyboard_clear_clipboard_lxx_light.png create mode 100644 app/src/main/res/drawable-xxxhdpi/sym_keyboard_clipboard_holo_dark.png create mode 100644 app/src/main/res/drawable-xxxhdpi/sym_keyboard_clipboard_lxx_dark.png create mode 100644 app/src/main/res/drawable-xxxhdpi/sym_keyboard_clipboard_lxx_light.png create mode 100644 app/src/main/res/layout/clipboard_entry_key.xml create mode 100644 app/src/main/res/layout/clipboard_history_view.xml diff --git a/app/build.gradle b/app/build.gradle index 33e8ae969..f188af090 100755 --- a/app/build.gradle +++ b/app/build.gradle @@ -43,6 +43,7 @@ android { dependencies { implementation 'com.google.code.findbugs:jsr305:3.0.2' implementation 'androidx.legacy:legacy-support-v4:1.0.0' + implementation 'androidx.recyclerview:recyclerview:1.2.1' // Replaces recyclerview:1.0.0 included by above dependency implementation 'androidx.core:core-ktx:1.5.0' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'androidx.viewpager2:viewpager2:1.0.0' diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/compat/ClipboardManagerCompat.java b/app/src/main/java/org/dslul/openboard/inputmethod/compat/ClipboardManagerCompat.java new file mode 100644 index 000000000..aba0ff874 --- /dev/null +++ b/app/src/main/java/org/dslul/openboard/inputmethod/compat/ClipboardManagerCompat.java @@ -0,0 +1,19 @@ +package org.dslul.openboard.inputmethod.compat; + +import android.annotation.TargetApi; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.os.Build; + +public class ClipboardManagerCompat { + + @TargetApi(Build.VERSION_CODES.P) + public static void clearPrimaryClip(ClipboardManager cm) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + cm.clearPrimaryClip(); + } else { + cm.setPrimaryClip(ClipData.newPlainText("", "")); + } + } + +} diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/KeyboardId.java b/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/KeyboardId.java index 96961a765..8be6c6281 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/KeyboardId.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/KeyboardId.java @@ -70,6 +70,7 @@ public final class KeyboardId { public static final int ELEMENT_EMOJI_CATEGORY14 = 24; public static final int ELEMENT_EMOJI_CATEGORY15 = 25; public static final int ELEMENT_EMOJI_CATEGORY16 = 26; + public static final int ELEMENT_CLIPBOARD = 27; public final RichInputMethodSubtype mSubtype; public final int mWidth; @@ -255,6 +256,7 @@ public final class KeyboardId { case ELEMENT_EMOJI_CATEGORY14: return "emojiCategory14"; case ELEMENT_EMOJI_CATEGORY15: return "emojiCategory15"; case ELEMENT_EMOJI_CATEGORY16: return "emojiCategory16"; + case ELEMENT_CLIPBOARD: return "clipboard"; default: return null; } } diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/KeyboardSwitcher.java b/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/KeyboardSwitcher.java index 633a3e52d..ba365d20f 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/KeyboardSwitcher.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/KeyboardSwitcher.java @@ -27,6 +27,7 @@ import android.view.inputmethod.EditorInfo; import org.dslul.openboard.inputmethod.event.Event; import org.dslul.openboard.inputmethod.keyboard.KeyboardLayoutSet.KeyboardLayoutSetException; +import org.dslul.openboard.inputmethod.keyboard.clipboard.ClipboardHistoryView; import org.dslul.openboard.inputmethod.keyboard.emoji.EmojiPalettesView; import org.dslul.openboard.inputmethod.keyboard.internal.KeyboardState; import org.dslul.openboard.inputmethod.keyboard.internal.KeyboardTextsSet; @@ -53,6 +54,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { private View mMainKeyboardFrame; private MainKeyboardView mKeyboardView; private EmojiPalettesView mEmojiPalettesView; + private ClipboardHistoryView mClipboardHistoryView; private LatinIME mLatinIME; private RichInputMethodManager mRichImm; private boolean mIsHardwareAcceleratedDrawingEnabled; @@ -135,7 +137,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { } public void saveKeyboardState() { - if (getKeyboard() != null || isShowingEmojiPalettes()) { + if (getKeyboard() != null || isShowingEmojiPalettes() || isShowingClipboardHistory()) { mState.onSaveKeyboardState(); } } @@ -288,6 +290,8 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { mMainKeyboardFrame.setVisibility(visibility); mEmojiPalettesView.setVisibility(View.GONE); mEmojiPalettesView.stopEmojiPalettes(); + mClipboardHistoryView.setVisibility(View.GONE); + mClipboardHistoryView.stopClipboardHistory(); } // Implements {@link KeyboardState.SwitchActions}. @@ -308,10 +312,30 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { mEmojiPalettesView.setVisibility(View.VISIBLE); } + // Implements {@link KeyboardState.SwitchActions}. + @Override + public void setClipboardKeyboard() { + if (DEBUG_ACTION) { + Log.d(TAG, "setClipboardKeyboard"); + } + final Keyboard keyboard = mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET); + mMainKeyboardFrame.setVisibility(View.GONE); + // The visibility of {@link #mKeyboardView} must be aligned with {@link #MainKeyboardFrame}. + // @see #getVisibleKeyboardView() and + // @see LatinIME#onComputeInset(android.inputmethodservice.InputMethodService.Insets) + mKeyboardView.setVisibility(View.GONE); + mClipboardHistoryView.startClipboardHistory( + mLatinIME.getClipboardHistoryManager(), + mKeyboardTextsSet.getText(KeyboardTextsSet.SWITCH_TO_ALPHA_KEY_LABEL), + mKeyboardView.getKeyVisualAttribute(), keyboard.mIconsSet); + mClipboardHistoryView.setVisibility(View.VISIBLE); + } + public enum KeyboardSwitchState { HIDDEN(-1), SYMBOLS_SHIFTED(KeyboardId.ELEMENT_SYMBOLS_SHIFTED), EMOJI(KeyboardId.ELEMENT_EMOJI_RECENTS), + CLIPBOARD(KeyboardId.ELEMENT_CLIPBOARD), OTHER(-1); final int mKeyboardId; @@ -322,15 +346,16 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { } public KeyboardSwitchState getKeyboardSwitchState() { - boolean hidden = !isShowingEmojiPalettes() + boolean hidden = !isShowingEmojiPalettes() && !isShowingClipboardHistory() && (mKeyboardLayoutSet == null || mKeyboardView == null || !mKeyboardView.isShown()); - KeyboardSwitchState state; if (hidden) { return KeyboardSwitchState.HIDDEN; } else if (isShowingEmojiPalettes()) { return KeyboardSwitchState.EMOJI; + } else if (isShowingClipboardHistory()) { + return KeyboardSwitchState.CLIPBOARD; } else if (isShowingKeyboardId(KeyboardId.ELEMENT_SYMBOLS_SHIFTED)) { return KeyboardSwitchState.SYMBOLS_SHIFTED; } @@ -348,10 +373,15 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { mLatinIME.startShowingInputView(true); if (toggleState == KeyboardSwitchState.EMOJI) { setEmojiKeyboard(); + } else if (toggleState == KeyboardSwitchState.CLIPBOARD) { + setClipboardKeyboard(); } else { mEmojiPalettesView.stopEmojiPalettes(); mEmojiPalettesView.setVisibility(View.GONE); + mClipboardHistoryView.stopClipboardHistory(); + mClipboardHistoryView.setVisibility(View.GONE); + mMainKeyboardFrame.setVisibility(View.VISIBLE); mKeyboardView.setVisibility(View.VISIBLE); setKeyboard(toggleState.mKeyboardId, toggleState); @@ -429,8 +459,12 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { return mEmojiPalettesView != null && mEmojiPalettesView.isShown(); } + public boolean isShowingClipboardHistory() { + return mClipboardHistoryView != null && mClipboardHistoryView.isShown(); + } + public boolean isShowingMoreKeysPanel() { - if (isShowingEmojiPalettes()) { + if (isShowingEmojiPalettes() || isShowingClipboardHistory()) { return false; } return mKeyboardView.isShowingMoreKeysPanel(); @@ -439,6 +473,8 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { public View getVisibleKeyboardView() { if (isShowingEmojiPalettes()) { return mEmojiPalettesView; + } else if (isShowingClipboardHistory()) { + return mClipboardHistoryView; } return mKeyboardView; } @@ -455,6 +491,9 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { if (mEmojiPalettesView != null) { mEmojiPalettesView.stopEmojiPalettes(); } + if (mClipboardHistoryView != null) { + mClipboardHistoryView.stopClipboardHistory(); + } } public View onCreateInputView(final boolean isHardwareAcceleratedDrawingEnabled) { @@ -467,8 +506,8 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { mCurrentInputView = (InputView)LayoutInflater.from(mThemeContext).inflate( R.layout.input_view, null); mMainKeyboardFrame = mCurrentInputView.findViewById(R.id.main_keyboard_frame); - mEmojiPalettesView = mCurrentInputView.findViewById( - R.id.emoji_palettes_view); + mEmojiPalettesView = mCurrentInputView.findViewById(R.id.emoji_palettes_view); + mClipboardHistoryView = mCurrentInputView.findViewById(R.id.clipboard_history_view); mKeyboardView = mCurrentInputView.findViewById(R.id.keyboard_view); mKeyboardView.setHardwareAcceleratedDrawingEnabled(isHardwareAcceleratedDrawingEnabled); @@ -476,6 +515,9 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { mEmojiPalettesView.setHardwareAcceleratedDrawingEnabled( isHardwareAcceleratedDrawingEnabled); mEmojiPalettesView.setKeyboardActionListener(mLatinIME); + mClipboardHistoryView.setHardwareAcceleratedDrawingEnabled( + isHardwareAcceleratedDrawingEnabled); + mClipboardHistoryView.setKeyboardActionListener(mLatinIME); return mCurrentInputView; } diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/clipboard/ClipboardAdapter.kt b/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/clipboard/ClipboardAdapter.kt new file mode 100644 index 000000000..41ae0dfa8 --- /dev/null +++ b/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/clipboard/ClipboardAdapter.kt @@ -0,0 +1,93 @@ +package org.dslul.openboard.inputmethod.keyboard.clipboard + +import android.graphics.Typeface +import android.util.TypedValue +import android.view.LayoutInflater +import android.view.MotionEvent +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import org.dslul.openboard.inputmethod.latin.ClipboardHistoryEntry +import org.dslul.openboard.inputmethod.latin.ClipboardHistoryManager +import org.dslul.openboard.inputmethod.latin.R + +class ClipboardAdapter( + val clipboardLayoutParams: ClipboardLayoutParams, + val keyEventListener: OnKeyEventListener +) : RecyclerView.Adapter() { + + var clipboardHistoryManager: ClipboardHistoryManager? = null + + var pinnedIconResId = 0 + var itemBackgroundId = 0 + var itemTypeFace: Typeface? = null + var itemTextColor = 0 + var itemTextSize = 0f + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val view = LayoutInflater.from(parent.context) + .inflate(R.layout.clipboard_entry_key, parent, false) + return ViewHolder(view) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + holder.setContent(getItem(position)) + } + + private fun getItem(position: Int) = clipboardHistoryManager?.getHistoryEntry(position) + + override fun getItemCount() = clipboardHistoryManager?.getHistorySize() ?: 0 + + inner class ViewHolder( + view: View + ) : RecyclerView.ViewHolder(view), View.OnClickListener, View.OnTouchListener, View.OnLongClickListener { + + private val pinnedIconView: ImageView + private val contentView: TextView + + init { + view.apply { + setOnClickListener(this@ViewHolder) + setOnTouchListener(this@ViewHolder) + setOnLongClickListener(this@ViewHolder) + setBackgroundResource(itemBackgroundId) + } + pinnedIconView = view.findViewById(R.id.clipboard_entry_pinned_icon).apply { + visibility = View.GONE + setImageResource(pinnedIconResId) + } + contentView = view.findViewById(R.id.clipboard_entry_content).apply { + typeface = itemTypeFace + setTextColor(itemTextColor) + setTextSize(TypedValue.COMPLEX_UNIT_PX, itemTextSize) + } + clipboardLayoutParams.setItemProperties(view) + } + + fun setContent(historyEntry: ClipboardHistoryEntry?) { + itemView.tag = historyEntry?.id + contentView.text = historyEntry?.content + pinnedIconView.visibility = if (historyEntry?.isPinned == true) View.VISIBLE else View.GONE + } + + override fun onTouch(view: View, event: MotionEvent): Boolean { + if (event.actionMasked != MotionEvent.ACTION_DOWN) { + return false + } + keyEventListener.onKeyDown(view.tag as Long) + return false + } + + override fun onClick(view: View) { + keyEventListener.onKeyUp(view.tag as Long) + } + + override fun onLongClick(view: View): Boolean { + clipboardHistoryManager?.toggleClipPinned(view.tag as Long) + return true + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/clipboard/ClipboardHistoryRecyclerView.kt b/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/clipboard/ClipboardHistoryRecyclerView.kt new file mode 100644 index 000000000..507b22930 --- /dev/null +++ b/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/clipboard/ClipboardHistoryRecyclerView.kt @@ -0,0 +1,78 @@ +package org.dslul.openboard.inputmethod.keyboard.clipboard + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.util.AttributeSet +import android.view.View +import androidx.recyclerview.widget.RecyclerView + +class ClipboardHistoryRecyclerView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : RecyclerView(context, attrs, defStyleAttr) { + + var placeholderView: View? = null + + private val adapterDataObserver: AdapterDataObserver = object : AdapterDataObserver() { + + override fun onChanged() { + checkAdapterContentChange() + } + + override fun onItemRangeChanged(positionStart: Int, itemCount: Int) { + checkAdapterContentChange() + } + + + override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { + checkAdapterContentChange() + } + + override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) { + checkAdapterContentChange() + } + + } + + private fun checkAdapterContentChange() { + if (placeholderView == null) return + val adapterIsEmpty = adapter == null || adapter?.itemCount == 0 + if (this@ClipboardHistoryRecyclerView.visibility == VISIBLE && adapterIsEmpty) { + placeholderView!!.visibility = VISIBLE + this@ClipboardHistoryRecyclerView.visibility = INVISIBLE + } else if (this@ClipboardHistoryRecyclerView.visibility == INVISIBLE && !adapterIsEmpty) { + placeholderView!!.visibility = INVISIBLE + this@ClipboardHistoryRecyclerView.visibility = VISIBLE + } + } + + override fun setAdapter(adapter: Adapter<*>?) { + this.adapter?.unregisterAdapterDataObserver(adapterDataObserver) + super.setAdapter(adapter) + checkAdapterContentChange() + adapter?.registerAdapterDataObserver(adapterDataObserver) + } + + class BottomDividerItemDecoration(dividerHeight: Int, dividerColor: Int) : RecyclerView.ItemDecoration() { + + private val paint = Paint() + + init { + paint.color = dividerColor + paint.strokeWidth = dividerHeight.toFloat() + } + + override fun onDrawOver(canvas: Canvas, parent: RecyclerView, state: State) { + super.onDrawOver(canvas, parent, state) + canvas.drawLine(parent.paddingLeft.toFloat(), + parent.height - paint.strokeWidth / 2, + parent.width.toFloat() - parent.paddingRight.toFloat(), + parent.height - paint.strokeWidth / 2 , + paint + ) + } + + } +} \ No newline at end of file diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/clipboard/ClipboardHistoryView.kt b/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/clipboard/ClipboardHistoryView.kt new file mode 100644 index 000000000..819942dcc --- /dev/null +++ b/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/clipboard/ClipboardHistoryView.kt @@ -0,0 +1,226 @@ +package org.dslul.openboard.inputmethod.keyboard.clipboard + +import android.content.Context +import android.util.AttributeSet +import android.util.TypedValue +import android.view.MotionEvent +import android.view.View +import android.widget.FrameLayout +import android.widget.ImageButton +import android.widget.LinearLayout +import android.widget.TextView +import androidx.recyclerview.widget.StaggeredGridLayoutManager +import org.dslul.openboard.inputmethod.keyboard.KeyboardActionListener +import org.dslul.openboard.inputmethod.keyboard.internal.KeyDrawParams +import org.dslul.openboard.inputmethod.keyboard.internal.KeyVisualAttributes +import org.dslul.openboard.inputmethod.keyboard.internal.KeyboardIconsSet +import org.dslul.openboard.inputmethod.latin.ClipboardHistoryManager +import org.dslul.openboard.inputmethod.latin.R +import org.dslul.openboard.inputmethod.latin.common.Constants +import org.dslul.openboard.inputmethod.latin.utils.ResourceUtils + +class ClipboardHistoryView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet?, + defStyle: Int = R.attr.clipboardHistoryViewStyle +) : LinearLayout(context, attrs, defStyle), View.OnTouchListener, View.OnClickListener, + ClipboardHistoryManager.OnHistoryChangeListener, OnKeyEventListener { + + private val clipboardLayoutParams = ClipboardLayoutParams(context.resources) + private val pinIconId: Int + private val dividerColor: Int + private val functionalKeyBackgroundId: Int + private val keyBackgroundId: Int + + private lateinit var clipboardRecyclerView: ClipboardHistoryRecyclerView + private lateinit var placeholderView: TextView + private lateinit var alphabetKey: TextView + private lateinit var clearKey: ImageButton + private lateinit var clipboardAdapter: ClipboardAdapter + + var keyboardActionListener: KeyboardActionListener? = null + var clipboardHistoryManager: ClipboardHistoryManager? = null + + init { + val clipboardViewAttr = context.obtainStyledAttributes(attrs, + R.styleable.ClipboardHistoryView, defStyle, R.style.ClipboardHistoryView) + pinIconId = clipboardViewAttr.getResourceId( + R.styleable.ClipboardHistoryView_iconPinnedClip, 0) + dividerColor = clipboardViewAttr.getColor( + R.styleable.ClipboardHistoryView_dividerBackground, 0) + clipboardViewAttr.recycle() + val keyboardViewAttr = context.obtainStyledAttributes(attrs, + R.styleable.KeyboardView, defStyle, R.style.KeyboardView) + keyBackgroundId = keyboardViewAttr.getResourceId( + R.styleable.KeyboardView_keyBackground, 0) + functionalKeyBackgroundId = keyboardViewAttr.getResourceId( + R.styleable.KeyboardView_functionalKeyBackground, keyBackgroundId) + keyboardViewAttr.recycle() + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + val res = context.resources + // The main keyboard expands to the entire this {@link KeyboardView}. + val width = (ResourceUtils.getDefaultKeyboardWidth(res) + + paddingLeft + paddingRight) + val height = (ResourceUtils.getDefaultKeyboardHeight(res) + + res.getDimensionPixelSize(R.dimen.config_suggestions_strip_height) + + paddingTop + paddingBottom) + setMeasuredDimension(width, height) + } + + override fun onFinishInflate() { + super.onFinishInflate() + clipboardAdapter = ClipboardAdapter(clipboardLayoutParams, this).apply { + itemBackgroundId = keyBackgroundId + pinnedIconResId = pinIconId + } + placeholderView = findViewById(R.id.clipboard_empty_view) + clipboardRecyclerView = findViewById(R.id.clipboard_list).apply { + val colCount = resources.getInteger(R.integer.config_clipboard_keyboard_col_count) + layoutManager = StaggeredGridLayoutManager(colCount, StaggeredGridLayoutManager.VERTICAL) + val dividerHeight = resources.getDimensionPixelSize(R.dimen.config_clipboard_divider_height) + addItemDecoration(ClipboardHistoryRecyclerView.BottomDividerItemDecoration(dividerHeight, dividerColor)) + persistentDrawingCache = PERSISTENT_NO_CACHE + clipboardLayoutParams.setListProperties(this) + placeholderView = this@ClipboardHistoryView.placeholderView + } + findViewById(R.id.clipboard_action_bar)?.apply { + clipboardLayoutParams.setActionBarProperties(this) + } + alphabetKey = findViewById(R.id.clipboard_keyboard_alphabet).apply { + tag = Constants.CODE_ALPHA_FROM_CLIPBOARD + setBackgroundResource(functionalKeyBackgroundId) + setOnTouchListener(this@ClipboardHistoryView) + setOnClickListener(this@ClipboardHistoryView) + } + clearKey = findViewById(R.id.clipboard_clear).apply { + setOnTouchListener(this@ClipboardHistoryView) + setOnClickListener(this@ClipboardHistoryView) + } + } + + private fun setupAlphabetKey(key: TextView?, label: String, params: KeyDrawParams) { + key?.apply { + text = label + typeface = params.mTypeface + setTextColor(params.mFunctionalTextColor) + setTextSize(TypedValue.COMPLEX_UNIT_PX, params.mLabelSize.toFloat()) + } + } + + private fun setupClipKey(params: KeyDrawParams) { + clipboardAdapter.apply { + itemBackgroundId = keyBackgroundId + itemTypeFace = params.mTypeface + itemTextColor = params.mTextColor + itemTextSize = params.mLabelSize.toFloat() + } + } + + private fun setupClearKey(iconSet: KeyboardIconsSet) { + val resId = iconSet.getIconResourceId(KeyboardIconsSet.NAME_CLEAR_CLIPBOARD_KEY) + clearKey.setImageResource(resId) + } + + fun setHardwareAcceleratedDrawingEnabled(enabled: Boolean) { + if (!enabled) return + // TODO: Should use LAYER_TYPE_SOFTWARE when hardware acceleration is off? + setLayerType(LAYER_TYPE_HARDWARE, null) + } + + fun startClipboardHistory( + historyManager: ClipboardHistoryManager, + switchToAlphaLabel: String, + keyVisualAttr: KeyVisualAttributes?, + iconSet: KeyboardIconsSet + ) { + historyManager.setHistoryChangeListener(this) + clipboardHistoryManager = historyManager + clipboardAdapter.clipboardHistoryManager = historyManager + + val params = KeyDrawParams() + params.updateParams(clipboardLayoutParams.actionBarContentHeight, keyVisualAttr) + setupAlphabetKey(alphabetKey, switchToAlphaLabel, params) + setupClipKey(params) + setupClearKey(iconSet) + + placeholderView.apply { + typeface = params.mTypeface + setTextColor(params.mTextColor) + setTextSize(TypedValue.COMPLEX_UNIT_PX, params.mLabelSize.toFloat() * 2) + } + clipboardRecyclerView.apply { + adapter = clipboardAdapter + } + } + + fun stopClipboardHistory() { + clipboardRecyclerView.adapter = null + clipboardHistoryManager?.setHistoryChangeListener(null) + clipboardHistoryManager = null + clipboardAdapter.clipboardHistoryManager = null + } + + override fun onTouch(view: View, event: MotionEvent): Boolean { + if (event.actionMasked != MotionEvent.ACTION_DOWN) { + return false + } + when (view) { + alphabetKey -> keyboardActionListener?.onPressKey( + Constants.CODE_ALPHA_FROM_CLIPBOARD, 0 /* repeatCount */, + true /* isSinglePointer */) + clearKey -> keyboardActionListener?.onPressKey( + Constants.CODE_UNSPECIFIED, 0 /* repeatCount */, + true /* isSinglePointer */) + } + // It's important to return false here. Otherwise, {@link #onClick} and touch-down visual + // feedback stop working. + return false + } + + override fun onClick(view: View) { + when (view) { + alphabetKey -> { + keyboardActionListener?.onCodeInput(Constants.CODE_ALPHA_FROM_CLIPBOARD, + Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, + false /* isKeyRepeat */) + keyboardActionListener?.onReleaseKey(Constants.CODE_ALPHA_FROM_CLIPBOARD, + false /* withSliding */) + } + clearKey -> { + clipboardHistoryManager?.clearHistory() + keyboardActionListener?.onReleaseKey(Constants.CODE_UNSPECIFIED, + false /* withSliding */) + } + } + } + + override fun onKeyDown(clipId: Long) { + keyboardActionListener?.onPressKey(Constants.CODE_UNSPECIFIED, 0 /* repeatCount */, + true /* isSinglePointer */) + } + + override fun onKeyUp(clipId: Long) { + val clipContent = clipboardHistoryManager?.getHistoryEntryContent(clipId) + keyboardActionListener?.onTextInput(clipContent?.content.toString()) + keyboardActionListener?.onReleaseKey(Constants.CODE_UNSPECIFIED, + false /* withSliding */) + } + + override fun onClipboardHistoryEntryAdded(at: Int) { + clipboardAdapter.notifyItemInserted(at) + clipboardRecyclerView.smoothScrollToPosition(at) + } + + override fun onClipboardHistoryEntriesRemoved(position: Int, count: Int) { + clipboardAdapter.notifyItemRangeRemoved(position, count) + } + + override fun onClipboardHistoryEntryMoved(from: Int, to: Int) { + clipboardAdapter.notifyItemMoved(from, to) + clipboardAdapter.notifyItemChanged(to) + if (to < from) clipboardRecyclerView.smoothScrollToPosition(to) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/clipboard/ClipboardLayoutParams.kt b/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/clipboard/ClipboardLayoutParams.kt new file mode 100644 index 000000000..e407a5cd0 --- /dev/null +++ b/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/clipboard/ClipboardLayoutParams.kt @@ -0,0 +1,68 @@ +package org.dslul.openboard.inputmethod.keyboard.clipboard + +import android.content.res.Resources +import android.view.View +import android.widget.FrameLayout +import android.widget.LinearLayout +import androidx.recyclerview.widget.RecyclerView +import org.dslul.openboard.inputmethod.latin.R +import org.dslul.openboard.inputmethod.latin.utils.ResourceUtils + +class ClipboardLayoutParams(res: Resources) { + + private val keyVerticalGap: Int + private val keyHorizontalGap: Int + private val topPadding: Int + private val bottomPadding: Int + private val listHeight: Int + private val actionBarHeight: Int + + companion object { + private const val DEFAULT_KEYBOARD_ROWS = 4 + } + + init { + val defaultKeyboardHeight = ResourceUtils.getDefaultKeyboardHeight(res) + val suggestionStripHeight = res.getDimensionPixelSize(R.dimen.config_suggestions_strip_height) + val defaultKeyboardWidth = ResourceUtils.getDefaultKeyboardWidth(res) + + keyVerticalGap = res.getFraction(R.fraction.config_key_vertical_gap_holo, + defaultKeyboardHeight, defaultKeyboardHeight).toInt() + bottomPadding = res.getFraction(R.fraction.config_keyboard_bottom_padding_holo, + defaultKeyboardHeight, defaultKeyboardHeight).toInt() + topPadding = res.getFraction(R.fraction.config_keyboard_top_padding_holo, + defaultKeyboardHeight, defaultKeyboardHeight).toInt() + keyHorizontalGap = res.getFraction(R.fraction.config_key_horizontal_gap_holo, + defaultKeyboardWidth, defaultKeyboardWidth).toInt() + + actionBarHeight = (defaultKeyboardHeight - bottomPadding - topPadding) / DEFAULT_KEYBOARD_ROWS - keyVerticalGap / 2 + listHeight = defaultKeyboardHeight + suggestionStripHeight - actionBarHeight - bottomPadding + } + + fun setListProperties(recycler: RecyclerView) { + (recycler.layoutParams as FrameLayout.LayoutParams).apply { + height = listHeight + recycler.layoutParams = this + } + } + + fun setActionBarProperties(layout: FrameLayout) { + (layout.layoutParams as LinearLayout.LayoutParams).apply { + height = actionBarHeight + layout.layoutParams = this + } + } + + fun setItemProperties(view: View) { + (view.layoutParams as RecyclerView.LayoutParams).apply { + topMargin = keyHorizontalGap / 2 + bottomMargin = keyVerticalGap / 2 + marginStart = keyHorizontalGap / 2 + marginEnd = keyHorizontalGap / 2 + view.layoutParams = this + } + } + + val actionBarContentHeight + get() = actionBarHeight +} \ No newline at end of file diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/clipboard/OnKeyEventListener.kt b/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/clipboard/OnKeyEventListener.kt new file mode 100644 index 000000000..5b1b2a9a8 --- /dev/null +++ b/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/clipboard/OnKeyEventListener.kt @@ -0,0 +1,9 @@ +package org.dslul.openboard.inputmethod.keyboard.clipboard + +interface OnKeyEventListener { + + fun onKeyDown(clipId: Long) + + fun onKeyUp(clipId: Long) + +} \ No newline at end of file diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/internal/KeyboardCodesSet.java b/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/internal/KeyboardCodesSet.java index 58f5b8415..9218b2e05 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/internal/KeyboardCodesSet.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/internal/KeyboardCodesSet.java @@ -53,6 +53,8 @@ public final class KeyboardCodesSet { "key_emoji", "key_alpha_from_emoji", "key_unspecified", + "key_clipboard", + "key_alpha_from_clipboard" }; private static final int[] DEFAULT = { @@ -73,6 +75,8 @@ public final class KeyboardCodesSet { Constants.CODE_EMOJI, Constants.CODE_ALPHA_FROM_EMOJI, Constants.CODE_UNSPECIFIED, + Constants.CODE_CLIPBOARD, + Constants.CODE_ALPHA_FROM_CLIPBOARD }; static { diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/internal/KeyboardIconsSet.java b/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/internal/KeyboardIconsSet.java index 5e2430a4f..d97223b3d 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/internal/KeyboardIconsSet.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/internal/KeyboardIconsSet.java @@ -52,7 +52,6 @@ public final class KeyboardIconsSet { public static final String NAME_PREVIOUS_KEY = "previous_key"; public static final String NAME_TAB_KEY = "tab_key"; public static final String NAME_SHORTCUT_KEY = "shortcut_key"; - public static final String NAME_CLIPBOARD_KEY = "clipboard_key"; public static final String NAME_INCOGNITO_KEY = "incognito_key"; public static final String NAME_SHORTCUT_KEY_DISABLED = "shortcut_key_disabled"; public static final String NAME_LANGUAGE_SWITCH_KEY = "language_switch_key"; @@ -60,6 +59,9 @@ public final class KeyboardIconsSet { public static final String NAME_ZWJ_KEY = "zwj_key"; public static final String NAME_EMOJI_ACTION_KEY = "emoji_action_key"; public static final String NAME_EMOJI_NORMAL_KEY = "emoji_normal_key"; + public static final String NAME_CLIPBOARD_ACTION_KEY = "clipboard_action_key"; + public static final String NAME_CLIPBOARD_NORMAL_KEY = "clipboard_normal_key"; + public static final String NAME_CLEAR_CLIPBOARD_KEY = "clear_clipboard_key"; private static final SparseIntArray ATTR_ID_TO_ICON_ID = new SparseIntArray(); @@ -81,7 +83,6 @@ public final class KeyboardIconsSet { NAME_PREVIOUS_KEY, R.styleable.Keyboard_iconPreviousKey, NAME_TAB_KEY, R.styleable.Keyboard_iconTabKey, NAME_SHORTCUT_KEY, R.styleable.Keyboard_iconShortcutKey, - NAME_CLIPBOARD_KEY, R.styleable.Keyboard_iconClipboardKey, NAME_INCOGNITO_KEY, R.styleable.Keyboard_iconIncognitoKey, NAME_SPACE_KEY_FOR_NUMBER_LAYOUT, R.styleable.Keyboard_iconSpaceKeyForNumberLayout, NAME_SHIFT_KEY_SHIFTED, R.styleable.Keyboard_iconShiftKeyShifted, @@ -91,6 +92,9 @@ public final class KeyboardIconsSet { NAME_ZWJ_KEY, R.styleable.Keyboard_iconZwjKey, NAME_EMOJI_ACTION_KEY, R.styleable.Keyboard_iconEmojiActionKey, NAME_EMOJI_NORMAL_KEY, R.styleable.Keyboard_iconEmojiNormalKey, + NAME_CLIPBOARD_ACTION_KEY, R.styleable.Keyboard_iconClipboardActionKey, + NAME_CLIPBOARD_NORMAL_KEY, R.styleable.Keyboard_iconClipboardNormalKey, + NAME_CLEAR_CLIPBOARD_KEY, R.styleable.Keyboard_iconClearClipboardKey, }; private static int NUM_ICONS = NAMES_AND_ATTR_IDS.length / 2; diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/internal/KeyboardState.java b/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/internal/KeyboardState.java index 8d8f7d84f..4b5177a27 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/internal/KeyboardState.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/internal/KeyboardState.java @@ -50,6 +50,7 @@ public final class KeyboardState { void setAlphabetShiftLockedKeyboard(); void setAlphabetShiftLockShiftedKeyboard(); void setEmojiKeyboard(); + void setClipboardKeyboard(); void setSymbolsKeyboard(); void setSymbolsShiftedKeyboard(); @@ -81,10 +82,11 @@ public final class KeyboardState { private static final int SWITCH_STATE_MOMENTARY_ALPHA_SHIFT = 5; private int mSwitchState = SWITCH_STATE_ALPHA; - // TODO: Consolidate these two mode booleans into one integer to distinguish between alphabet, - // symbols, and emoji mode. - private boolean mIsAlphabetMode; - private boolean mIsEmojiMode; + private static final int MODE_ALPHABET = 0; + private static final int MODE_SYMBOLS = 1; + private static final int MODE_EMOJI = 2; + private static final int MODE_CLIPBOARD = 3; + private int mMode = MODE_ALPHABET; private AlphabetShiftState mAlphabetShiftState = new AlphabetShiftState(); private boolean mIsSymbolShifted; private boolean mPrevMainKeyboardWasShiftLocked; @@ -99,9 +101,8 @@ public final class KeyboardState { static final class SavedKeyboardState { public boolean mIsValid; - public boolean mIsAlphabetMode; public boolean mIsAlphabetShiftLocked; - public boolean mIsEmojiMode; + public int mMode; public int mShiftMode; @Override @@ -109,13 +110,16 @@ public final class KeyboardState { if (!mIsValid) { return "INVALID"; } - if (mIsAlphabetMode) { + if (mMode == MODE_ALPHABET) { return mIsAlphabetShiftLocked ? "ALPHABET_SHIFT_LOCKED" : "ALPHABET_" + shiftModeToString(mShiftMode); } - if (mIsEmojiMode) { + if (mMode == MODE_EMOJI) { return "EMOJI"; } + if (mMode == MODE_CLIPBOARD) { + return "CLIPBOARD"; + } return "SYMBOLS_" + shiftModeToString(mShiftMode); } } @@ -152,9 +156,8 @@ public final class KeyboardState { public void onSaveKeyboardState() { final SavedKeyboardState state = mSavedKeyboardState; - state.mIsAlphabetMode = mIsAlphabetMode; - state.mIsEmojiMode = mIsEmojiMode; - if (mIsAlphabetMode) { + state.mMode = mMode; + if (mMode == MODE_ALPHABET) { state.mIsAlphabetShiftLocked = mAlphabetShiftState.isShiftLocked(); state.mShiftMode = mAlphabetShiftState.isAutomaticShifted() ? AUTOMATIC_SHIFT : (mAlphabetShiftState.isShiftedOrShiftLocked() ? MANUAL_SHIFT : UNSHIFT); @@ -175,7 +178,7 @@ public final class KeyboardState { + " " + stateToString(autoCapsFlags, recapitalizeMode)); } mPrevMainKeyboardWasShiftLocked = state.mIsAlphabetShiftLocked; - if (state.mIsAlphabetMode) { + if (state.mMode == MODE_ALPHABET) { setAlphabetKeyboard(autoCapsFlags, recapitalizeMode); setShiftLocked(state.mIsAlphabetShiftLocked); if (!state.mIsAlphabetShiftLocked) { @@ -183,10 +186,14 @@ public final class KeyboardState { } return; } - if (state.mIsEmojiMode) { + if (state.mMode == MODE_EMOJI) { setEmojiKeyboard(); return; } + if (state.mMode == MODE_CLIPBOARD) { + setClipboardKeyboard(); + return; + } // Symbol mode if (state.mShiftMode == MANUAL_SHIFT) { setSymbolsShiftedKeyboard(); @@ -199,7 +206,7 @@ public final class KeyboardState { if (DEBUG_INTERNAL_ACTION) { Log.d(TAG, "setShifted: shiftMode=" + shiftModeToString(shiftMode) + " " + this); } - if (!mIsAlphabetMode) return; + if (mMode != MODE_ALPHABET) return; final int prevShiftMode; if (mAlphabetShiftState.isAutomaticShifted()) { prevShiftMode = AUTOMATIC_SHIFT; @@ -238,7 +245,7 @@ public final class KeyboardState { if (DEBUG_INTERNAL_ACTION) { Log.d(TAG, "setShiftLocked: shiftLocked=" + shiftLocked + " " + this); } - if (!mIsAlphabetMode) return; + if (mMode != MODE_ALPHABET) return; if (shiftLocked && (!mAlphabetShiftState.isShiftLocked() || mAlphabetShiftState.isShiftLockShifted())) { mSwitchActions.setAlphabetShiftLockedKeyboard(); @@ -254,7 +261,7 @@ public final class KeyboardState { Log.d(TAG, "toggleAlphabetAndSymbols: " + stateToString(autoCapsFlags, recapitalizeMode)); } - if (mIsAlphabetMode) { + if (mMode == MODE_ALPHABET) { mPrevMainKeyboardWasShiftLocked = mAlphabetShiftState.isShiftLocked(); if (mPrevSymbolsKeyboardWasShifted) { setSymbolsShiftedKeyboard(); @@ -279,7 +286,7 @@ public final class KeyboardState { Log.d(TAG, "resetKeyboardStateToAlphabet: " + stateToString(autoCapsFlags, recapitalizeMode)); } - if (mIsAlphabetMode) return; + if (mMode == MODE_ALPHABET) return; mPrevSymbolsKeyboardWasShifted = mIsSymbolShifted; setAlphabetKeyboard(autoCapsFlags, recapitalizeMode); @@ -303,8 +310,7 @@ public final class KeyboardState { } mSwitchActions.setAlphabetKeyboard(); - mIsAlphabetMode = true; - mIsEmojiMode = false; + mMode = MODE_ALPHABET; mIsSymbolShifted = false; mRecapitalizeMode = RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE; mSwitchState = SWITCH_STATE_ALPHA; @@ -316,7 +322,7 @@ public final class KeyboardState { Log.d(TAG, "setSymbolsKeyboard"); } mSwitchActions.setSymbolsKeyboard(); - mIsAlphabetMode = false; + mMode = MODE_SYMBOLS; mIsSymbolShifted = false; mRecapitalizeMode = RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE; // Reset alphabet shift state. @@ -329,7 +335,7 @@ public final class KeyboardState { Log.d(TAG, "setSymbolsShiftedKeyboard"); } mSwitchActions.setSymbolsShiftedKeyboard(); - mIsAlphabetMode = false; + mMode = MODE_SYMBOLS; mIsSymbolShifted = true; mRecapitalizeMode = RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE; // Reset alphabet shift state. @@ -341,8 +347,7 @@ public final class KeyboardState { if (DEBUG_INTERNAL_ACTION) { Log.d(TAG, "setEmojiKeyboard"); } - mIsAlphabetMode = false; - mIsEmojiMode = true; + mMode = MODE_EMOJI; mRecapitalizeMode = RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE; // Remember caps lock mode and reset alphabet shift state. mPrevMainKeyboardWasShiftLocked = mAlphabetShiftState.isShiftLocked(); @@ -350,6 +355,18 @@ public final class KeyboardState { mSwitchActions.setEmojiKeyboard(); } + private void setClipboardKeyboard() { + if (DEBUG_INTERNAL_ACTION) { + Log.d(TAG, "setClipboardKeyboard"); + } + mMode = MODE_CLIPBOARD; + mRecapitalizeMode = RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE; + // Remember caps lock mode and reset alphabet shift state. + mPrevMainKeyboardWasShiftLocked = mAlphabetShiftState.isShiftLocked(); + mAlphabetShiftState.setShiftLocked(false); + mSwitchActions.setClipboardKeyboard(); + } + public void onPressKey(final int code, final boolean isSinglePointer, final int autoCapsFlags, final int recapitalizeMode) { if (DEBUG_EVENT) { @@ -379,7 +396,7 @@ public final class KeyboardState { // As for #3, please note that it's required to check even when the auto caps mode is // off because, for example, we may be in the #1 state within the manual temporary // shifted mode. - if (!isSinglePointer && mIsAlphabetMode + if (!isSinglePointer && mMode == MODE_ALPHABET && autoCapsFlags != TextUtils.CAP_MODE_CHARACTERS) { final boolean needsToResetAutoCaps = mAlphabetShiftState.isAutomaticShifted() || (mAlphabetShiftState.isManualShifted() && mShiftKeyState.isReleasing()); @@ -463,7 +480,7 @@ public final class KeyboardState { } private void updateAlphabetShiftState(final int autoCapsFlags, final int recapitalizeMode) { - if (!mIsAlphabetMode) return; + if (mMode != MODE_ALPHABET) return; if (RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE != recapitalizeMode) { // We are recapitalizing. Match the keyboard to the current recapitalize state. updateShiftStateForRecapitalize(recapitalizeMode); @@ -490,7 +507,7 @@ public final class KeyboardState { if (RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE != mRecapitalizeMode) { return; } - if (mIsAlphabetMode) { + if (mMode == MODE_ALPHABET) { mIsInDoubleTapShiftKey = mSwitchActions.isInDoubleTapShiftKeyTimeout(); if (!mIsInDoubleTapShiftKey) { // This is first tap. @@ -541,7 +558,7 @@ public final class KeyboardState { // We are recapitalizing. We should match the keyboard state to the recapitalize // state in priority. updateShiftStateForRecapitalize(mRecapitalizeMode); - } else if (mIsAlphabetMode) { + } else if (mMode == MODE_ALPHABET) { final boolean isShiftLocked = mAlphabetShiftState.isShiftLocked(); mIsInAlphabetUnshiftedFromShifted = false; if (mIsInDoubleTapShiftKey) { @@ -629,7 +646,7 @@ public final class KeyboardState { case SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL: if (code == Constants.CODE_SWITCH_ALPHA_SYMBOL) { // Detected only the mode change key has been pressed, and then released. - if (mIsAlphabetMode) { + if (mMode == MODE_ALPHABET) { mSwitchState = SWITCH_STATE_ALPHA; } else { mSwitchState = SWITCH_STATE_SYMBOL_BEGIN; @@ -644,8 +661,8 @@ public final class KeyboardState { } break; case SWITCH_STATE_SYMBOL_BEGIN: - if (mIsEmojiMode) { - // When in the Emoji keyboard, we don't want to switch back to the main layout even + if (mMode == MODE_EMOJI || mMode == MODE_CLIPBOARD) { + // When in the Emoji keyboard or clipboard one, we don't want to switch back to the main layout even // after the user hits an emoji letter followed by an enter or a space. break; } @@ -671,6 +688,10 @@ public final class KeyboardState { setEmojiKeyboard(); } else if (code == Constants.CODE_ALPHA_FROM_EMOJI) { setAlphabetKeyboard(autoCapsFlags, recapitalizeMode); + } else if (code == Constants.CODE_CLIPBOARD) { + setClipboardKeyboard(); + } else if (code == Constants.CODE_ALPHA_FROM_CLIPBOARD) { + setAlphabetKeyboard(autoCapsFlags, recapitalizeMode); } } @@ -697,7 +718,7 @@ public final class KeyboardState { @Override public String toString() { - return "[keyboard=" + (mIsAlphabetMode ? mAlphabetShiftState.toString() + return "[keyboard=" + (mMode == MODE_ALPHABET ? mAlphabetShiftState.toString() : (mIsSymbolShifted ? "SYMBOLS_SHIFTED" : "SYMBOLS")) + " shift=" + mShiftKeyState + " symbol=" + mSymbolKeyState diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/internal/KeyboardTextsTable.java b/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/internal/KeyboardTextsTable.java index 88035ea24..957cc798f 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/internal/KeyboardTextsTable.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/internal/KeyboardTextsTable.java @@ -260,6 +260,8 @@ public final class KeyboardTextsTable { /* 174: 0 */ "morekeys_tablet_double_quote", /* 175: 0 */ "keyspec_emoji_action_key", /* 176: 0 */ "keyspec_emoji_normal_key", + /* 177: 0 */ "keyspec_clipboard_action_key", + /* 178: 0 */ "keyspec_clipboard_normal_key", }; private static final String EMPTY = ""; @@ -482,6 +484,8 @@ public final class KeyboardTextsTable { /* morekeys_tablet_double_quote */ "!fixedColumnOrder!6,!text/double_quotes,!text/single_quotes,!text/double_angle_quotes,!text/single_angle_quotes", /* keyspec_emoji_action_key */ "!icon/emoji_action_key|!code/key_emoji", /* keyspec_emoji_normal_key */ "!icon/emoji_normal_key|!code/key_emoji", + /* keyspec_clipbaord_action_key */ "!icon/clipboard_action_key|!code/key_clipboard", + /* keyspec_clipboard_normal_key */ "!icon/clipboard_normal_key|!code/key_clipboard", }; /* Locale af: Afrikaans */ diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/ClipboardHistoryEntry.kt b/app/src/main/java/org/dslul/openboard/inputmethod/latin/ClipboardHistoryEntry.kt new file mode 100644 index 000000000..5f0719071 --- /dev/null +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/ClipboardHistoryEntry.kt @@ -0,0 +1,13 @@ +package org.dslul.openboard.inputmethod.latin + +data class ClipboardHistoryEntry ( + var id: Long, + val content: CharSequence, + var isPinned: Boolean = false +) : Comparable { + + override fun compareTo(other: ClipboardHistoryEntry): Int { + val result = other.isPinned.compareTo(isPinned) + return if (result != 0) result else other.id.compareTo(id) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/ClipboardHistoryManager.kt b/app/src/main/java/org/dslul/openboard/inputmethod/latin/ClipboardHistoryManager.kt new file mode 100644 index 000000000..514af5f8c --- /dev/null +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/ClipboardHistoryManager.kt @@ -0,0 +1,166 @@ +package org.dslul.openboard.inputmethod.latin + +import android.content.ClipboardManager +import android.content.Context +import android.text.TextUtils +import android.util.Base64 +import android.util.Log +import org.dslul.openboard.inputmethod.compat.ClipboardManagerCompat +import org.dslul.openboard.inputmethod.latin.utils.JsonUtils +import java.io.File +import java.lang.Exception +import java.util.* + +class ClipboardHistoryManager( + private val latinIME: LatinIME +) : ClipboardManager.OnPrimaryClipChangedListener { + + private lateinit var pinnedHistoryClipsFile: File + private lateinit var clipboardManager: ClipboardManager + private val historyEntries: MutableList + private var onHistoryChangeListener: OnHistoryChangeListener? = null + + fun onCreate() { + pinnedHistoryClipsFile = File(latinIME.filesDir, PINNED_CLIPS_DATA_FILE_NAME) + clipboardManager = latinIME.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + fetchPrimaryClip() + clipboardManager.addPrimaryClipChangedListener(this) + startLoadPinnedClipsFromDisk() + } + + fun onPinnedClipsAvailable(pinnedClips: List) { + historyEntries.addAll(pinnedClips) + sortHistoryEntries() + if (onHistoryChangeListener != null) { + pinnedClips.forEach { + onHistoryChangeListener?.onClipboardHistoryEntryAdded(historyEntries.indexOf(it)) + } + } + } + + fun onDestroy() { + clipboardManager.removePrimaryClipChangedListener(this) + } + + override fun onPrimaryClipChanged() = fetchPrimaryClip() + + private fun fetchPrimaryClip() { + if (!clipboardManager.hasPrimaryClip()) return + val clipData = clipboardManager.primaryClip + if (clipData != null && clipData.itemCount > 0 && clipData.getItemAt(0) != null) { + val content = clipData.getItemAt(0).coerceToText(latinIME) + if (!TextUtils.isEmpty(content)) { + val id = System.nanoTime() + val entry = ClipboardHistoryEntry(id, content) + historyEntries.add(entry) + sortHistoryEntries() + val at = historyEntries.indexOf(entry) + onHistoryChangeListener?.onClipboardHistoryEntryAdded(at) + } + } + } + + fun toggleClipPinned(clipId: Long) { + val from = historyEntries.indexOfFirst { it.id == clipId } + val historyEntry = historyEntries[from].apply { + id = System.nanoTime() + isPinned = !isPinned + } + sortHistoryEntries() + val to = historyEntries.indexOf(historyEntry) + onHistoryChangeListener?.onClipboardHistoryEntryMoved(from, to) + startSavePinnedClipsToDisk() + } + + fun clearHistory() { + ClipboardManagerCompat.clearPrimaryClip(clipboardManager) + val pos = historyEntries.indexOfFirst { !it.isPinned } + val count = historyEntries.count { !it.isPinned } + historyEntries.removeAll { !it.isPinned } + if (onHistoryChangeListener != null) { + onHistoryChangeListener?.onClipboardHistoryEntriesRemoved(pos, count) + } + } + + private fun sortHistoryEntries() { + historyEntries.sort() + } + + fun getHistorySize() = historyEntries.size + + fun getHistoryEntry(position: Int) = historyEntries[position] + + fun getHistoryEntryContent(id: Long) = historyEntries.first { it.id == id } + + fun setHistoryChangeListener(l: OnHistoryChangeListener?) { + onHistoryChangeListener = l + } + + private fun startLoadPinnedClipsFromDisk() { + object : Thread("$TAG-load") { + override fun run() { + loadFromDisk() + } + }.start() + } + + private fun loadFromDisk() { + // Debugging + if (pinnedHistoryClipsFile.exists() && !pinnedHistoryClipsFile.canRead()) { + Log.w(TAG, "Attempt to read pinned clips file $pinnedHistoryClipsFile without permission") + } + var list = emptyList() + try { + if (pinnedHistoryClipsFile.exists()) { + val bytes = Base64.decode(pinnedHistoryClipsFile.readText(), Base64.DEFAULT) + list = JsonUtils.jsonBytesToHistoryEntryList(bytes) + } + } catch (e: Exception) { + Log.w(TAG, "Couldn't retrieve $pinnedHistoryClipsFile content", e) + } + latinIME.mHandler.postUpdateClipboardPinnedClips(list) + } + + private fun startSavePinnedClipsToDisk() { + val localCopy = historyEntries.filter { it.isPinned }.map { it.copy() } + object : Thread("$TAG-save") { + override fun run() { + saveToDisk(localCopy) + } + }.start() + } + + private fun saveToDisk(list: List) { + // Debugging + if (pinnedHistoryClipsFile.exists() && !pinnedHistoryClipsFile.canWrite()) { + Log.w(TAG, "Attempt to write pinned clips file $pinnedHistoryClipsFile without permission") + } + try { + pinnedHistoryClipsFile.createNewFile() + val jsonStr = JsonUtils.historyEntryListToJsonStr(list) + if (!TextUtils.isEmpty(jsonStr)) { + val rawText = Base64.encodeToString(jsonStr.encodeToByteArray(), Base64.DEFAULT) + pinnedHistoryClipsFile.writeText(rawText) + } else { + pinnedHistoryClipsFile.writeText("") + } + } catch (e: Exception) { + Log.w(TAG, "Couldn't write to $pinnedHistoryClipsFile", e) + } + } + + interface OnHistoryChangeListener { + fun onClipboardHistoryEntryAdded(at: Int) + fun onClipboardHistoryEntriesRemoved(pos: Int, count: Int) + fun onClipboardHistoryEntryMoved(from: Int, to: Int) + } + + companion object { + const val PINNED_CLIPS_DATA_FILE_NAME = "pinned_clips.data" + const val TAG = "ClipboardHistoryManager" + } + + init { + historyEntries = LinkedList() + } +} \ No newline at end of file diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/LatinIME.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/LatinIME.java index 1285380d0..b104e64cb 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/LatinIME.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/LatinIME.java @@ -211,6 +211,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private GestureConsumer mGestureConsumer = GestureConsumer.NULL_GESTURE_CONSUMER; + private final ClipboardHistoryManager mClipboardHistoryManager = new ClipboardHistoryManager(this); + public final UIHandler mHandler = new UIHandler(this); public static final class UIHandler extends LeakGuardHandlerWrapper { @@ -226,8 +228,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private static final int MSG_DEALLOCATE_MEMORY = 9; private static final int MSG_RESUME_SUGGESTIONS_FOR_START_INPUT = 10; private static final int MSG_SWITCH_LANGUAGE_AUTOMATICALLY = 11; + private static final int MSG_UPDATE_CLIPBOARD_PINNED_CLIPS = 12; // Update this when adding new messages - private static final int MSG_LAST = MSG_SWITCH_LANGUAGE_AUTOMATICALLY; + private static final int MSG_LAST = MSG_UPDATE_CLIPBOARD_PINNED_CLIPS; private static final int ARG1_NOT_GESTURE_INPUT = 0; private static final int ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 1; @@ -324,6 +327,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen case MSG_SWITCH_LANGUAGE_AUTOMATICALLY: latinIme.switchLanguage((InputMethodSubtype)msg.obj); break; + case MSG_UPDATE_CLIPBOARD_PINNED_CLIPS: + @SuppressWarnings("unchecked") + List entries = (List) msg.obj; + latinIme.mClipboardHistoryManager.onPinnedClipsAvailable(entries); + break; } } @@ -446,6 +454,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen obtainMessage(MSG_SWITCH_LANGUAGE_AUTOMATICALLY, subtype).sendToTarget(); } + public void postUpdateClipboardPinnedClips(final List clips) { + obtainMessage(MSG_UPDATE_CLIPBOARD_PINNED_CLIPS, clips).sendToTarget(); + } + // Working variables for the following methods. private boolean mIsOrientationChanging; private boolean mPendingSuccessiveImsCallback; @@ -612,6 +624,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mStatsUtilsManager.onCreate(this /* context */, mDictionaryFacilitator); super.onCreate(); + mClipboardHistoryManager.onCreate(); mHandler.onCreate(); // TODO: Resolve mutual dependencies of {@link #loadSettings()} and @@ -750,6 +763,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen @Override public void onDestroy() { + mClipboardHistoryManager.onDestroy(); mDictionaryFacilitator.closeDictionaries(); mSettings.onDestroy(); unregisterReceiver(mHideSoftInputReceiver); @@ -1226,6 +1240,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen return; } final int suggestionsHeight = (!mKeyboardSwitcher.isShowingEmojiPalettes() + && !mKeyboardSwitcher.isShowingClipboardHistory() && mSuggestionStripView.getVisibility() == View.VISIBLE) ? mSuggestionStripView.getHeight() : 0; final int visibleTopY = inputHeight - visibleKeyboardView.getHeight() - suggestionsHeight; @@ -1828,6 +1843,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } }; + public ClipboardHistoryManager getClipboardHistoryManager() { + return mClipboardHistoryManager; + } + void launchSettings() { mInputLogic.commitTyped(mSettings.getCurrent(), LastComposedWord.NOT_A_SEPARATOR); requestHideSelf(0); diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/common/Constants.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/common/Constants.java index eb75d987f..7311d8b55 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/common/Constants.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/common/Constants.java @@ -242,11 +242,13 @@ public final class Constants { public static final int CODE_ACTION_PREVIOUS = -9; public static final int CODE_LANGUAGE_SWITCH = -10; public static final int CODE_EMOJI = -11; - public static final int CODE_SHIFT_ENTER = -12; - public static final int CODE_SYMBOL_SHIFT = -13; - public static final int CODE_ALPHA_FROM_EMOJI = -14; + public static final int CODE_CLIPBOARD = -12; + public static final int CODE_SHIFT_ENTER = -13; + public static final int CODE_SYMBOL_SHIFT = -14; + public static final int CODE_ALPHA_FROM_EMOJI = -15; + public static final int CODE_ALPHA_FROM_CLIPBOARD = -16; // Code value representing the code is not specified. - public static final int CODE_UNSPECIFIED = -15; + public static final int CODE_UNSPECIFIED = -17; public static boolean isLetterCode(final int code) { return code >= CODE_SPACE; @@ -266,8 +268,10 @@ public final class Constants { case CODE_ACTION_PREVIOUS: return "actionPrevious"; case CODE_LANGUAGE_SWITCH: return "languageSwitch"; case CODE_EMOJI: return "emoji"; + case CODE_CLIPBOARD: return "clipboard"; case CODE_SHIFT_ENTER: return "shiftEnter"; case CODE_ALPHA_FROM_EMOJI: return "alpha"; + case CODE_ALPHA_FROM_CLIPBOARD: return "alpha"; case CODE_UNSPECIFIED: return "unspec"; case CODE_TAB: return "tab"; case CODE_ENTER: return "enter"; diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/inputlogic/InputLogic.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/inputlogic/InputLogic.java index 5af4e72af..31bcc6bf2 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/inputlogic/InputLogic.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/inputlogic/InputLogic.java @@ -692,6 +692,14 @@ public final class InputLogic { // Note: Switching back from Emoji keyboard to the main keyboard is being // handled in {@link KeyboardState#onEvent(Event,int)}. break; + case Constants.CODE_CLIPBOARD: + // Note: Switching clipboard keyboard is being handled in + // {@link KeyboardState#onEvent(Event,int)}. + break; + case Constants.CODE_ALPHA_FROM_CLIPBOARD: + // Note: Switching back from clipboard keyboard to the main keyboard is being + // handled in {@link KeyboardState#onEvent(Event,int)}. + break; case Constants.CODE_SHIFT_ENTER: final Event tmpEvent = Event.createSoftwareKeypressEvent(Constants.CODE_ENTER, event.getMKeyCode(), event.getMX(), event.getMY(), event.isKeyRepeat()); diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/suggestions/SuggestionStripView.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/suggestions/SuggestionStripView.java index e2c1a2e0c..875dbdd39 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/suggestions/SuggestionStripView.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/suggestions/SuggestionStripView.java @@ -40,6 +40,7 @@ import android.widget.TextView; import org.dslul.openboard.inputmethod.accessibility.AccessibilityUtils; import org.dslul.openboard.inputmethod.keyboard.Keyboard; +import org.dslul.openboard.inputmethod.keyboard.KeyboardSwitcher; import org.dslul.openboard.inputmethod.keyboard.MainKeyboardView; import org.dslul.openboard.inputmethod.keyboard.MoreKeysPanel; import org.dslul.openboard.inputmethod.latin.AudioAndHapticFeedbackManager; @@ -167,7 +168,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick R.styleable.Keyboard, defStyle, R.style.SuggestionStripView); final Drawable iconVoice = keyboardAttr.getDrawable(R.styleable.Keyboard_iconShortcutKey); final Drawable iconIncognito = keyboardAttr.getDrawable(R.styleable.Keyboard_iconIncognitoKey); - final Drawable iconClipboard = keyboardAttr.getDrawable(R.styleable.Keyboard_iconClipboardKey); + final Drawable iconClipboard = keyboardAttr.getDrawable(R.styleable.Keyboard_iconClipboardNormalKey); keyboardAttr.recycle(); mVoiceKey.setImageDrawable(iconVoice); mVoiceKey.setOnClickListener(this); @@ -442,12 +443,8 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick return; } if (view == mClipboardKey) { - CharSequence selectionSequence = mListener.getSelection(); - if (selectionSequence != null && selectionSequence.length() > 0 - && !Settings.getInstance().getCurrent().mInputAttributes.mIsPasswordField) { - ClipboardManager clipboardManager = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE); - clipboardManager.setPrimaryClip(ClipData.newPlainText(selectionSequence, selectionSequence)); - } + final KeyboardSwitcher switcher = KeyboardSwitcher.getInstance(); + switcher.onToggleKeyboard(KeyboardSwitcher.KeyboardSwitchState.CLIPBOARD); return; } diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/JsonUtils.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/JsonUtils.java index f084ac39f..6851794a0 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/JsonUtils.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/JsonUtils.java @@ -16,15 +16,20 @@ package org.dslul.openboard.inputmethod.latin.utils; +import android.text.TextUtils; import android.util.JsonReader; import android.util.JsonWriter; import android.util.Log; +import org.dslul.openboard.inputmethod.latin.ClipboardHistoryEntry; +import java.io.ByteArrayInputStream; import java.io.Closeable; import java.io.IOException; +import java.io.InputStreamReader; import java.io.StringReader; import java.io.StringWriter; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; @@ -33,6 +38,8 @@ public final class JsonUtils { private static final String INTEGER_CLASS_NAME = Integer.class.getSimpleName(); private static final String STRING_CLASS_NAME = String.class.getSimpleName(); + private static final String CLIPBOARD_HISTORY_ENTRY_ID_KEY = "id"; + private static final String CLIPBOARD_HISTORY_ENTRY_CONTENT_KEY = "content"; private static final String EMPTY_STRING = ""; @@ -91,6 +98,63 @@ public final class JsonUtils { return EMPTY_STRING; } + public static List jsonBytesToHistoryEntryList(final byte[] bytes) { + final ArrayList list = new ArrayList<>(); + final JsonReader reader = new JsonReader(new InputStreamReader(new ByteArrayInputStream(bytes))); + try { + reader.beginArray(); + while (reader.hasNext()) { + reader.beginObject(); + long id = 0; + String content = EMPTY_STRING; + while (reader.hasNext()) { + final String name = reader.nextName(); + if (name.equals(CLIPBOARD_HISTORY_ENTRY_ID_KEY)) { + id = reader.nextLong(); + } else if (name.equals(CLIPBOARD_HISTORY_ENTRY_CONTENT_KEY)) { + content = reader.nextString(); + } else { + Log.w(TAG, "Invalid name: " + name); + reader.skipValue(); + } + } + if (id > 0 && !TextUtils.isEmpty(content)) { + list.add(new ClipboardHistoryEntry(id, content, true)); + } + reader.endObject(); + } + reader.endArray(); + return list; + } catch (final IOException e) { + } finally { + close(reader); + } + return Collections.emptyList(); + } + + public static String historyEntryListToJsonStr(final Collection entries) { + if (entries == null || entries.isEmpty()) { + return EMPTY_STRING; + } + final StringWriter sw = new StringWriter(); + final JsonWriter writer = new JsonWriter(sw); + try { + writer.beginArray(); + for (final ClipboardHistoryEntry e : entries) { + writer.beginObject(); + writer.name(CLIPBOARD_HISTORY_ENTRY_ID_KEY).value(e.getId()); + writer.name(CLIPBOARD_HISTORY_ENTRY_CONTENT_KEY).value(e.getContent().toString()); + writer.endObject(); + } + writer.endArray(); + return sw.toString(); + } catch (final IOException e) { + } finally { + close(writer); + } + return EMPTY_STRING; + } + private static void close(final Closeable closeable) { try { if (closeable != null) { diff --git a/app/src/main/res/drawable-hdpi/ic_clipboard_pin_holo_dark.png b/app/src/main/res/drawable-hdpi/ic_clipboard_pin_holo_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..e23aa3492db65e998b0d6b42f4916815a3a65fd1 GIT binary patch literal 566 zcmV-60?GY}P)VZ%Zf>cscb!QdwZ%ls6` zyyPXM(|JFo9flSP0ZWJ|ipZj6N*gzzQK!>cPm-hx)-16~O)n*2Qi9=;QmPGh!JZ+z zU>lmp)RunM0h2YDooM4v@XL_Y=u(r~($6|36flMN0^_&P3;6LnPbP3KHN4(Je26z- zxDVq7_^Q%dAuBkCa~ss6rk979pivyhyp>{=K8W)c=uv}O-olT-wVO*IL+CJBF)C1} zmehBSP#_dAL?{pn7$Ou11q=}itWbeko&^pN;z@_e3eI_i#-aBDQM#RuV19uRLzyfC zhSZ>z^l%1-$DfZq7`G6hFB8pyKJ;2Bu`9itKwo)=AIKyI80Zo^($6Yz^*3R3rtDmK z34Rmj^pL*R0C8S@t~yRYZF)#wYtRJ}X>1GB_EmsO;4DhW@jvuc;2vhrD*XYQMvjj! zulzi74z*nuDB(eilk|mbeHszw6ubrBHSc&#o+ntNHa(=TRnP#Guf~6frI2s#wh{Ph ztiTPAaZH{s=r;U+fDi?Q-I`FK%&~<>D3CWofxHn4DyG6MsSY>Pw*Tf~Li?R(|$901~``C*h@H#R?E=P>UWu#} zF}1%a_@3GKr${Rw1Wfqtj~7z*M708Q2};q`iPV>%;PbN~<{9MkI}(MAO{BjDqnVOC zvu1Pza}g@Oex|<6l;vo;5FGUd#`rmwM4J(4-P<#p^-@eZvGka(GkTHnYSSno)5Saj zKhIPI^e7_#+Le@A?L6=XUmARakriIj_K?lbGZi(BMCIbg>D78sTCG>Fv~pIBBFa!a~1LtXF7X-7^H9g8BkL0k3OxOX6fwt zb@j+XCbIc?mV`#vu$zp}cGJ6#K#z4t0+4cg9Q{z4r8CCYwM<#v!dOxBo^AgF${f9; zfe-n*p2-0+lLKU?J_XYH4M^Q;0hJSX4fJ3ela|H4R~b z1Oq6W66_zUi6jy>CEha@ZEuM)9Vf+vImt(s z2%1WjpNh=puE=igxia-4!xJcFd~!0jwEsz(jkZy!lWQk~x!y)EGLXgd2UdXYDtfv? znU8zM0zoa%AV;pZ(Su%OAdBa_5`wS~f|10v4%8XLx$7sWMGtyCgXtE6u_vrY(Fedx zHK;`o&+$w?kez%WJN>6XbnFXGj{OjTg4*;|eSW(k9RQ&QwdmnFo@i7{grn*A%hGHf zIa(pAL2Wc$SsvA%td64E9_s#1#GJE~#n-N5ufQ3kR^x>7znGd;g?y|31) zUymaTnaJkz90`p|NG0R9-$6Aoq35<2Zwlx}nJjvXc*FCm+a89iSDw5;{N+&KoEsX3B-w;`bK4(Q8xb%1r{g#OsDC`kaW0 sxwXe)cJ;pNPYbU_s}erfM|Gj}6KUomZtT&N=l}o!07*qoM6N<$f_LdUlK=n! literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/sym_keyboard_clear_clipboard_holo_dark.png b/app/src/main/res/drawable-hdpi/sym_keyboard_clear_clipboard_holo_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..138984cafc223b913e49d7cb326be912281ff5a8 GIT binary patch literal 1124 zcmV-q1e^PbP)Gs;f?Qs|&V#cv zaQb{p^YHH1h3lJNg{eAdmsG=bj0Y&P9MVfT^c6XW}h0@ zbIPy}d&`v~0b`R;-bi~%aOd6|5@Y!LPTGx_IHn1#DFSQk^XQeK;{zkXbTaPx#YN(o zULeg#yfh>ljs%ZNO+xgynLY&CTdvPXRzQM`D$x@yrW2nD<7%RimINbl5B+;|s3vw{uNjg6^Y}%YrHK&or4u8;=VuO> zuL$bSVqT{P3q^ptw1hsMPWFiHiG*|FLU8kg5a2@=ZEkK(wY!&{L?V|?mywVJ7PvT^ zoJ9LE`o1X=xpY!%mt>Ob?n>Ew=B-(^QoKlnXh2{h5SOT*3(aU=$vQ#KMjVpq`rQOzNSat~+% zeES-sLGGFZYZzO_`qGK7oxCSx4#9SlYjW-21l)MXkOohucDEESOef!52N}N_0tD}g zB)jZdZmu_-`blsCbl#+pq(Ubhpu3c2Wk=uIsYr%Fpy(mi52bwv@TQYbhDAV*Q98x( zDyWoB!y+ICbc)nLA&H?7fN{R-wvzHsBY(>UamJxAoq`i^<0QflwzeSi0#35tFD=Do z?IcUla0D(Qaq)L2TLAKun@zJDyQ4D_1SnN6J9lHRq}hGKuXv+DfZ`%inoe=|Lji4% z4gpHlbc)?~C)jR{6ah+&bQJB zPF`^YP89(mVe6hj;8L}dS3qC_1a{1#!-hAVDhcxwla?`g-z{|2s87P3!^?~W(<$k# zZ9=1R?bKJ?X~BQAL5dZS+&ex;lE|x$I65nJx59Ukd^hb=e)__jMflKm8$u qBybZy#P75nyEcMdAG3Y`jQj!WV-R8*ciTV!00000v@6b;x$>v!8n`~W{TLRK#P+6COZX>}(QD^{V6Hdb(3EiMdJsNzCY6v2gVbs-p9 znviO()$RKc??()t^WL3iGVkRj>3fM@ICN(2ojWIUXU-eVWd3J!9B05DYh{SdMo_C30n`87PHYD0xtJTip?>^gS8yCdlCqd$)Qfe8f305%niM~XnKw>wN6p`>N z{@zD_)bw-bfFTX|4`bn1h~EHy13v_8Z@~AOG3Fc#Su=mgurtSz=riz#iG|cKeIx__ z87wjl+y&-<1;njuGUzV?PYhD)=syDP$s7~2$UlbsBQ}pMGj?c2NY&7v!uWpikD$MV z@l~J>d^Z+;2LB--$F@O~F^rAED)N=vk>UX*sj4KbpnugMbqxIv82<@u3jEz+d8CYs z95ehHB&o@0!Xz~wTRt*I(s=~0$)sgWx`pwB;wPz>D){wW$lnRfc?Y~T=GkOkz&}N> zEn{``OFUCHk1eBDAcT~^TWHU9ONfhT zQa6qA9YO!O3U-Pmj6)SUCy{$6!M5b@nh?o+QGvdY6w7CxDUy1K_%iS~1`CM0h}?ro z2@xgqdC)0--2QJ!KFO_@_&ptBP~wvjLi>85P{@JAgbJ=aNmbFG$%{@IbDPA|g8xZM zg%Fc)aWA-iJTolcnvl{vWs9eYB4W=A;dBT|q8l=08X=M~O2#s<986qN!L#L;kZKZ7+tMUtA8Hd+ zbp0U>iKmsnFN93!uYD`=OpVY07Px@^Z6;{}z7FSAphF??l>F%s5?|b>!H0$b6jpLbx5MF2%FSoZuFWsm5c>{O3y& zbS$1BldD@+;df11h!xLB6H?>Ya=xi;0Zf7@@tj6wcG(<4#`5t@r49M9T0A$=euMG$ zrR$}!OeBTfdXM%A+y74tM24V4qfV0lN9u?D1(M|?30zyb=l}o!07*qoM6N<$f=n>U A0RR91 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/sym_keyboard_clear_clipboard_lxx_light.png b/app/src/main/res/drawable-hdpi/sym_keyboard_clear_clipboard_lxx_light.png new file mode 100644 index 0000000000000000000000000000000000000000..9b16dc28b8d6e96e381c453f92680d6b1cb990b6 GIT binary patch literal 1104 zcmV-W1h4yvP)Di4jfPKmm8^N*Bh&5EB<_To_|q zu+ZqzuyAc6dnncf5K$fyzwdSihI3m=d#}Nhe7Vy(XU_d*=FE4eH8uaUS(a5R`%0P5 z=d1Di{mqd`FdokptjY_5ZPiELBgO&~CeT0D z+uM6dPI`}Vg%C8QmY<{+qtR#zgHF(oXDdj(!L}>`tB6_CD+400F}{sp+gPbJ#ufvJ z<&pnsIZ{pBHU!(GfO(L-hJKqAp6wtti-a4%HlYZ;Z;{^I<6e+G$ zmNg}L>=>QgcQq6Hg(D*)+`dVJ)LE?5E0WeRz=neUZDN%~Fs_kMr0mr~X-wcWmL)V{ z5ZYo=))ZID*uFDZskbDR+&y8X@Es&l$bE#sfk|sZm~2IaNM;ULCMj+oYf9@BNS(&- zX$azT;MqQSg1Ga@?Nfv@)e<7^p-^vw&;m&T^Vq%{Al0Daj|0aJ09z$p6XM`tFxZ9} zUe~7&&m6Wdt98m4OOpbqVW}ARgqVbqfR%cjRQkh02dQymrKEV8Vwgdm!2(~82oWgE zG1ae+`-0j&yQZXg8d${Gn>vqYgwP&EUyl7C^h?)N3Gw8|Rujr`tk;BUySlofOiUfY z_Dvf#CB?H1q^=@1#qk`&vCQEaA?33Z5_oAd3tgW+DV~U_*Rgs+s{h{*B0`}MKc@?H6i7*ICwwsK4soKrFbGm zJtZNbbjlT>gPL*k%;}mE;z^yFMgLNz_o1o?(WpltHOf<`PoKPb@-u^e-jgaKl&*#l zjVrRBc*5V>-Q68yDaf%qCJE`QCrMRZ2P;wz&8Y$h7Egy^Ia~NA4l^Msnb;MWFIh%BiyDGj_$Lm7vOJnn1gk|U z@noq;4i2*lR|tprxrGqVKREs$@gDo+9hy-*NeTz%6WSfpe<%hbfp9o{vfRl(bo&cj W!h%NLit+sb0000`Xb|$+{ zCfOF|!eP_=Irp3-oeY`LXo$N}Rn?QpWU@7z&2|B0>TJJz7ZN#=*lme^U)S~5`F#Ej zP^OMH`rONqxDAQim)I{7eIfidxS)+b^tI#H8qBJMZA;`QC-zV98CKbFuD+=>#;9AaV^!*=;t-)y0Ml|Q(;*krWv zW8ex+R%dHC1JfWOgl`bz1Ra_r7L2^m75Ub z@6Q1LNI>OkesjuNAzi^~T?PEv4eY7zgv34J1kO?hICWX(9pQzrn+P~|;DkH70oR@d zjyu1BoKeI8zXI?0_T41~x7=9`0PR-}sPlUH`^+kU<0qV+zs_wSNI-iZXyeajz@G#D z4KoFBto0o5?=M}kmUG}S37IwO69LB_oN#A1;M)C@s89IZ55s`X>4Ubc2C5#mkbh*_ zyhI5DDgVTa{u+NSo-Lb~C}kk!F^Zt;HbJEfq#R`2NVEznVj$sFV$omY|BC0C%}ZoA z;My^g@-o*bo~TL~$QgqHVla>pg8`z=0GCcA$cbDRTU_%ULR#4dMpBl&W7 zvrfBS?+c6kXhD?mdI%xFZRVjRloK>*Gwqlgm=y^VM z));7tOG)=k@Czy;Y=3BYtHbS)ZLD%E;5uE0+BB8!yM5eWE~%1ug?^5(En{jSZzV8) zOW2k%-As^91N|av>OOu#43&7XB;@eR7m0{*oCS4wbNe^APA?sfmrV^FU~G<{z3+Z+ z<+~xz$jvG`Sw%mzp*Z$?8~qA6HKc;v9coxJBy$v82uWNttQv}OJ|r>Guw#gVSoSUS z^PD#%&s=KQG(@2c#%lhY0X60-I@4(=ie=xVFvkqZFqazE49T1)7?P-HOw^F~&(iem z7;?I|EIR;hQ%W~w(~#*+%^SI(7n`J^hjAO?gE7S-6d8g7-+hj}G8BC~hGfq4)$z-W zXxKAkBW8vMg$+aA(UU?p2z!JLI^^pQzOWb{^80-T*<$$5AjXg%ilag^=L0_!{SZ@W xE@`Lxp*Jc;Y7cG34=yG5cTDu(rdj-__yWqPypwERc7p%_002ovPDHLkV1kRj6FL9@ literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/sym_keyboard_clipboard_lxx_light.png b/app/src/main/res/drawable-hdpi/sym_keyboard_clipboard_lxx_light.png new file mode 100644 index 0000000000000000000000000000000000000000..318f4407a068d2a724eb02f816e4a23eb65f56bf GIT binary patch literal 728 zcmV;}0w?{6P)1gh6op-69b$_LL4#=NC@p#Y6UTowJOC8HD+EC-AUp-4i%5YG6=fQD1l}WwQxKvE zh=e%bYR0TCD`$5-1`%@_FY>3u;c(mFh5Rq|`vn2|J=Uze1+<-yzE_O3@oay?YeOIF9ss4r8R3f${-I?sUq?^#h0Qy#rDAAv8U8I#E)--*UQC#Tgh zGdiiWe;NohtychTNZiv|e0Q*Z@Eeck*J|9>YRoUF-EMEmw+6J&JQn%U;Ma=EizA76 z;(6fo{NwSsu%L+}s;b_%C2B`_QxH3fy#t36G+9`X>x1xeNhG^u6j%^uH@Lir97h;I z5USl%-^6ZoIWSS|1{@&;t?9+wlyBen6~KXFA9s)hMhjwm1+hsi z9Ki&+zJ#zORvgD)6_l(j!yiUaRIFrMEC7AYla*!og9$41QtNYZpzqYgCJQQvrzVk2 zo(0A!*-`~Xlz^v(BnQ|)HbazL}=%9;~Y{($C>o)CpV zsUz$lb;uWz!#PjIR4)iN%)*EKdDoy&NUlK=GmYbT{xt}57sXuyc!~_dh2Ul;xhU!` zH~*sOje)3VN~v?k##ah(!3lS6G2WO;>YK6QEd)5vEz^Jdm+U7=yijp8j=aGD0000< KMNUMnLSTX`=vq7g literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_clipboard_pin_holo_dark.png b/app/src/main/res/drawable-mdpi/ic_clipboard_pin_holo_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..74d1ede9770132be00351307ed65b4b5125d23d8 GIT binary patch literal 414 zcmV;P0b%}$P)yQ#ZOO2oik1Y9V~2ZDySQ z5%S=H#C*T+nMs0^Wv8G#&pUFrraP9?NPx<+ys4^cD32mNq^6Y+f?NuC@0?r8>qsxD zq1N(h%L}k_@mjSRfJbmE=v;oYodROcoSIf10V;~3r(ZO2`{n&kYvxj8IgQ1xW^)0z z-2x)Oh#JdjtP~&xNCEy!fT@eSj=6@Xtp?Dob|vVe;9J`*pe=K$v7AN{=vw#sgTNcx z=?iVBq1N(hNz2$5XaUf6h!7zbFf2D(D$cc>FP0oDYGGJzvp{rYsY11Y6!^O<8p;Q#;t07*qo IM6N<$f`AFT4FCWD literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/sym_keyboard_clear_clipboard_holo_dark.png b/app/src/main/res/drawable-mdpi/sym_keyboard_clear_clipboard_holo_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..838e6f4c3a24def737b6f6db7cbedf4637cfbbdb GIT binary patch literal 777 zcmV+k1NQuhP)L%tQSMAk%0(ge0=XrAK@0LBKeK z&!xnZykRDA{&SfoCkpbz0_}$o?27aRssFjgzsT=Gx(NViUoeJ` z_mA{Q3YQ(30erVWKzvjnCe(Yq-k!F(E8R(dHGXY=zxDeSqHTTG_>|~YI#%56QV4qm zFAd_>AU*&gHPqfy-?;=p_T&n9(rh-j5N-NwjQNQ6m5KHx5bQ*}er8%|T?_FvOdvof zfT*keR()p{%tL%e!Q$+W*DsB=5pBlV30_CM_Rz>=w#)zsI0?d!lM6l{LIKw%VUa;x z5ql~Cg8_iwx-y7!=`sL_)lr<5;x&-kUl#>n3s2oBxnc;C@12xRl6+n(UTs+bo}Tx& z>qe(z2$5LPnxUG-Tn>Oq%J5A)9Y14;V9u~kM9O?B0Wg`xEP&7Sa3>0N!gM2_HV~Bo zEWn}5ldAxcj_O3m<+;P_S#i~KsPNv#MTcA#inE?3m9lQL@Gci2(i)yYqF=hhbioOlRAiqi%&ipbmpb%nmZUMng@H*~8 zC3WIV$+(-?jp;!wI31_@d#K-!W1p295PI?#>5Tt9AKd-{7DSod(Jz$p00000NkvXX Hu0mjfz(r$& literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/sym_keyboard_clipboard_holo_dark.png b/app/src/main/res/drawable-mdpi/sym_keyboard_clipboard_holo_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..5cdbfd527cc426a3d4083e04cc45c9ec98f23340 GIT binary patch literal 522 zcmV+l0`>igP)Mi|6I=*^1#$vS_>n(7Fo&2!OssFHAx(kKG(BoesN^L* zHC3`(L@QS3f2on`sETgXti1mz#8A4I5&R+H#KbCjj>gz)r1#W_WHwd3Hc}7x$DaiQ1lqSOWcRVZ%szQ0@PphBH9_tN)ZrZM*$%M(t!ek5F9d) z*pbYhcVBpoWc7IeP#Oxj3Js;XWBGw7HkM-qppvaB8F1QiNBevG1Tnk=*vsjG9RL6T M07*qoM6N<$f`)$G_5c6? literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_clipboard_pin_holo_dark.png b/app/src/main/res/drawable-xhdpi/ic_clipboard_pin_holo_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..191d744929a3d76219c490122a130961725a6b52 GIT binary patch literal 573 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/sym_keyboard_clipboard_holo_dark.png b/app/src/main/res/drawable-xhdpi/sym_keyboard_clipboard_holo_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..595577a2244298a4ec9e2e43c5fbe743701c2eb0 GIT binary patch literal 936 zcmV;Z16TZsP)hQ?CyHK z4Ib%}wqAQ?zRzRtZtAVAVtK5ps%^oJ!M47?u_k064Tr<0)9Li(Y&LrjxTcMJy8p%) zvn(W^3)xRX`qz9u|Kox-?$KxCDndx@2)Q>x^4t)y(eI^3IYUVCXa;BSP|uVqM(Hs z%h(U%N##K0;PnSI272~YyuMe zEo486?_DR(E^XX1ee}3kkn1H?E57glG+{kaQl0=>byxj$p!`3}?CjY%T+_xqPk$eD zTwnafa)5)&DazPXSXat9*KAtMmJ8oT<^z409iH`10N1odj1P%_P3~~eyn@XLKsi~* zHaHvDNH8=Q(dJ2_4)~nNxNpFW3t3_@@e$xAg#fJK5oOET1*U{aB4!QKkLuDDwhA%v zlL|2OY+^%|KnldfF9PHcfXAgR>r@!8h@VXWzV_Zq`>Eh-Vw@6-m`?n>0+_z`TcHaq zz1&oN5Fn=&0g3=U5P-kw-x2P_&ME-c{O}>|yx!h2CK2#GA(oCm%PoMk@yicibGCm9 z;G3_>UJ9TSNP(F6Sp`UmkoZ{yIFx%ACW(}|AeJY7Rss0w>;;K&FEI{@MNB7tUI9#B z{@J-PAReca3rxgK;07ZZzKoOt_Pz2~yfTbfUxzY6z8Q&qL+(>#zic=ZZsVf3% z_X+xh8Ui#vCP!(-9F)wBr98@?F!_|dEo&ZG_NRg4of2f&b8`&139*H zSlXHqVL(Z-uGn09iuRAoTcea%Pi!hZ2RSw?`(nk46)RTk|FC~A#BUOrzh@l)0000< KMNUMnLSTXiNUucz literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/sym_keyboard_clipboard_lxx_dark.png b/app/src/main/res/drawable-xhdpi/sym_keyboard_clipboard_lxx_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..fd3d22c2c082bc66075aca0d6d3108cf001a0fab GIT binary patch literal 851 zcmV-Z1FZasP)H$;UKJKLN_r>Dn4?Y`;qp61O6en-cq8q7Q^01_!$KL2b5ZYx`elXjEGg zwkDBpo!DQ(Pxl*^x%N(N@6acDD9MU&Q@B+GriMoMxf}VzKy(OihCA^>eK2v3%YfYF z?v@f<(?~s1vTfm57#H9~!j7DH#C{jvsQw|KEPQjcwg>cmOmHa7*fyz%*_a(SaTiZViv>h;zbKMDU9qxl}VgXKpwHV>&w$ z2%o8L6R6xv|D6&R^y8WM>JUC3St#Piz<5yuyAW$STm>!(BV~nz2uC|_#C{80_uS6x4KTZIbHhqEpNymeVw)`hzjVI5AmqZ@G7ryIA5 z;d93~5Ho5qz^}j_zfIc(0S8=X+))hx%~$p~Lt#X_1p&tpPR~DyZNMeiOaFgQQv^8n zGWeq#VEN12PRs*D@~_SEM>Vjax*eCK3hcmHmjT`b*GWiSMc9B7?&t>Gb-xl-37>m8 z4A`7LxE9qwQo$7ROQy|B)M3Cs#QpSiC9)4>5ophnITX*T%}dl}AY4XCG7tERI1*3W z<|XPhQ1(SOJ(0Lj0}E^=I7&HD|KIxOm3gui-GIA}k+Pe)HsS|^!Pyo%jHC_&RYaC} zVrdNIMPqp{%uL@>J=(Ul~mSRhbZ3KSB5uD~mB=UsRk+=DFC9jwKbEKC3w{3OI^ zqDJL6zE3j;$LTqRa?Ttf>6g4Y)A?q;?|o;cb0%{#nVz*9X{I7 z*6sgaZD3#^GdemttkHfWqDR8n?Ck6rPp6FUt=itBk9X5_-x6-;a=D>SC>D!%v`n5a zEG&Gn2p=sjE`A1-0}=D{^ItU+FVyF*8^;B?;o$PRlqo@OdV0DxGc)r_N%utVpc9(S z<~NJ{q58bmb4Af!o}s+(NIlCC0f_A_#G^Ti(oC4T_t}? zVj)|v*Pl=)|EaySE&{nFWL;e9UCjhjhx&j#Nlo$=RM#RB0sOmKQjfLkARwo@N49`4 zn3y)sMZg74F3(6~{V|0Bc|DJX9bwym^07-INGkkP|7ET9hd@hcnL2AQ+&+=(N=blN#3Z(N0r*X#zJ$@rolQf4PfJAr zPoeGXa#aw^j_p+d(2=+z-v`35+D{aiS1ngei8+vl>~@I zOpM<*LV%kyFnnGG#9cz{Ho=zZ=Eb}VaQ?&`#7J~$nCLu1CISJ?q)cvOk7R&&}>^f-(RA002ovPDHLkV1hwt!bSi9 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_clipboard_pin_holo_dark.png b/app/src/main/res/drawable-xxhdpi/ic_clipboard_pin_holo_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..b53f4ba936e5d90dfa616df7366836c0d2a9fe38 GIT binary patch literal 835 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7Ro>VCM34aSW-5dpqZB-em`o)@V)^ zj}4!NSQZKWkh~QdRUvrIRp*!52Hjc?xkddCSc8IB+H>z|+;)J=ZO-K$*O>x}LFUJe zE$5e*Z(Td5+CE)M-7hSx$MBd$@)_RXP|?@d4ruqrvgNGFoK>BBd);C)_NdoqB9^e2 zZ0%y3-zM-a(0#?@Mp5Ip%8#WsFZ;P!ovWfv?f44$74w<3)1;Iif4;;jbF8hM%~&?7 zfsb1tnK5aa+X0I||I}?nrahbgA!^1Ro~Nhx35Q5ahaWti#+Ih}FEyiFaoa^dy{foH z4`r_`Uo9MIAoGuJ^AR8Eqj4RAW?@IY|Lt1$-i@=yOk}9Ai2X(I5e~n?%7*CO4po#hR>FN_`pkSpU-COTPHQ zuzzMF&<@vN-+kI2B$#hF*Zwco<8JNW+pgn3BQx`W6c_~B~BsVIKtNX}6^;a#cf^L{!@)g2E{sg8eBRFZnr(%V{I;Hj=Vb6i$&efGjA(POZ@~Oulw}V4%;QQsmzDMGn&G8MIL(dM)9q-^dg2jNh|Le zcbQsX-t-bWu)Xg0s``oV?8~PrJy4!y>H5*Ua$R(d=Wok1ZMS#$SKYBGJ@sZM_m1Ax zCv{COxtH!Y{37;M*XQv5y@&1eAMJbj@qg@@b8?sVZEE6OaA`jKyw$IE?dw0d=iq|# zmtQs)=RLN2S$KWLp7#6Z5*+nUUY7p&{o70Y&av7{kA3`Mjvzj)7d5;!_!(fgx4&Kf zRDSdU-lm854o-+qyuqIM%WfVs*s^8n26wJ5%alvHu(%8;D7d|F{uhhO$}$C}wg3IX h<@SM+tB=?prehAj(kx{+ngcTjgQu&X%Q~loCIEoBWmW(H literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_clipboard_pin_lxx_dark.png b/app/src/main/res/drawable-xxhdpi/ic_clipboard_pin_lxx_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..ba419856fc8e20358fb2b8177d3f20890500ad97 GIT binary patch literal 1092 zcmV-K1iSl*P)aqk&puT=J+6qG&=}v_QOPpqN~#H%jNBJ-_56q@i>E_t&|%_fEiKK@bG_k0`P8 zMWl+2D4X3?MCUOT<$}lA*cY#ouFQBz`E;$ziZ{^xvYHb_7oE!#aBgu|pl58fb?Cbi zk{4zTspvPQp=az7WTEpJ3c0l%{;sj86iLrZk<_xWm7XVAh9DE2(=bXLJl}dlnGDIP z<>2+Cp#>Qr3uK~m8U_kturkHQ*PUYY0P$k1Nm}uReZl#z$&@`|TyaR+6%EPLUqa3wlCt$Ux^Z6*jU_v^n@< zvL~{V{mFj#bTt$-3v##}9xL>O-gF+*0y|sS!08~!2Xg5CrUwNvKtT*p5Cas%00l8X zK@3n30~Evn1u;ND3{Vh*RY6`9lmiHT?7HHE=QLUh3SL%f;KQK%n;x6pwW;aCbgdLg zYxX<{uH7^+eE@pV{Y{U}&TnqMm=rgrw4nJ(jvNW8Ppi z`BqCg*;0GR$+yrGdYc;i_I=iar|*l>aPo05lFHD7(W;1~4D^KFkb%x+7`8HJQHg`! zM7~8zIk#Bh?|y#h__eREB{{8|oNPf)=zX~J#UaQ7nbd3~Bb{3pI6<`5pM54u@`r;h z7pKp*3?`nm4KIFFZ+RW2)-R=g?WdL@17v|rkPR|IRyx0?kWZ%g=-?Y~$DPG@?YEXb zk{j5x2MAO>2W)bmw_34&$~{c*8zU^3R&KQTpW@^6a_BC(!i^)-2JL)RuX z)pVX@497jXpZGx%*=x8qQsfL(Aze{w={pgAZW~F>_#+RzTZO&9yzfs-cRBeY*Hl#n zk^Ri!@XN|AWls8Sg+s2zeoatM3fPxsHAOjMVqW!YF4Qwv=wO)SbNR+`7FAr&TUM5`rKI@{=FYR^~eVgcQC20000< KMNUMnLSTZ2Ui=XN literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_clipboard_pin_lxx_light.png b/app/src/main/res/drawable-xxhdpi/ic_clipboard_pin_lxx_light.png new file mode 100644 index 0000000000000000000000000000000000000000..e8a83751a8961a6cf7841d5905584184abe63e5f GIT binary patch literal 1098 zcmV-Q1hxB#P)cxU-2*pomDA@@QPRbLAhPF5DOs6L&6L zXhMkbkr0SRiW)RNc=d@$0ktX9n+U-7`6w zEEKIM=*==bkd<&FpU+Yxx3a~5`tdcYXXiCNyKH4a2LGui*C7+1(=sHF*Gqe*CulOa ztaW89EyzH7G82%A&uJMYK^YXZ9;G4ngblIW>LIqqMO3sl!Vm*~ouu={mHK3=BoM-S zl>6Csk}dEgm&Z(u$l=>dR5+1wEm+ZC~?cV6i5-i6oP~GFzYQ zH(tNy2`YjdS&7Kd)AkF+pMjMvEa0jjC=u=Bme~oKtTdfkN^}U z00jv^K>|>afE7Ve6toVK$1BmUjOkC1cHzUmjVZ{jxPl#T4$x%Iy#Isr>?eBe)j{aR z_qRRluGVQC@8eX@eO`5O6)HHl+VL(9z4-n%$Lo+uL{rnkspGVJW|o>UyjBX%4L#JBsd$|-QokW2Ny>IdD1I**OlDu3DW_b}Bbdy; z4#l5GC1occ$6h$eZdq=-9ie@NeinR*zZkO$deOewe(3EayJS9>WjNgp@Fj;(K6h1ck+4io4@5$pQ-`$kZ!f9s zZw6@ZT%k0q%pY2-%XsZC)4sU^WPwbO4KhMjKEJ8JBGBJ98Vz1t91EuAXV`*mp6Z#U zYRoyJG9*D3mdVJ*GWuFZV@|fR=dZ&wzVEZTi$(Tm8;dqRsO4pd%CXgUk!?rDtGn7Z zeIr__vTDDEf&`epQ?FDR!^{dnY?4+J>4@6)awL#QPqLc$+hX&?^jeZ(vJG^O|I@iy zO{#69BZ21Oy;T(ifF!nyCjU}03HutmrVsE>qVYEDF61{IyAzeR?p$@;pI4RTR0y016U-f&`!-0VqfS3KH-;LGX{FmUt1Wc|pGBY*f<=nO_CvmV>Feaev}r zQ1aBUpwfp{=?34)v0Iv+Uoa%W@Ji-q17`=X$fRjAe?$!eA6zvx`c6H*tLeFglANKd zG6}2vFKZemX@}PO@&a(G;u8XP+g-&E?h?ZIKS9v}%p5Cr+bHzF!`os4$T QdH?_b07*qoM6N<$f<^H2mjD0& literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/sym_keyboard_clear_clipboard_holo_dark.png b/app/src/main/res/drawable-xxhdpi/sym_keyboard_clear_clipboard_holo_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..66f563955684ff2070443f3b7cb8dc9988a2853a GIT binary patch literal 1910 zcmV-+2Z{KJP)uwY`7$sbqqM#6xY1IoMMO4+$_774;D^*dEN>SA|z@Iii0tAs3$Xn(m@(}wFR?nHS z73a%byfgOhc-BWcN_Kqv>@#ybCCYxs zI%K`k0Ks2DCvtRV7iZP?c>R&g6AIdL?xXF4ngMSk%N(!{)+Os7JoIG8!GEDU^BS-3 zQWTljHz+jP`iX7ow&mcu(!~AqEKi=^(1X9F%1hr20RO(FC?Q2a-51fuCfeGo7|^cV zL!Gx+w`^~4&}Sdde0%T>`T7;ogV+)07}ULtHhxj%geu#YvDlR9c0NwhzoG77-yI=1 zPPpHwQ`?c9F&%QYBYg+;G04t0l3tCu<`dNas-=DY<)GTM(9M|;87b<@4JS!&Ds78dGCXtYjU!FW~Iw=rtCY}UPm5+ zkH9TnRTu-=_3qFXbXS%4+;yEX9Wr)wS=M8Kv?b)>S^cL~u8p!SyUktHcbjqQGmuZm zjQKXx9LoD5>!VFRMBD4^r|f%Ya6nJaPr_AX8Yj5TrU_1qa2O zNtp3%Fl~yue3SFjA>s+M>|xmvJJ{4ob0vhbBj)=DoxKO%(!9rGQ`FH!o@CE!$TXSX zkOCY96M(>=T|*-u*jPmXXM#~-?TCD%$}^ti|1!M`Il!Cfc?WWEn%aP#)VC8It zja3978QP9Q@Hq#XCk7{Vn>?mP;FTcDk%JSU2ug*uBi}3pXt>LYPJkjX72b|~ag+iS z!KpaxXtV+p0jlBK(WnI|f>hn?XjY6~06vAbt;I}hM}MgD){pOyg(jEHS7R1{1NEbF zU$$toBZ{z!HopM5Cnz*OLwlEE7J$KRAkSCIU4%wEV#hPR}={N6`vEjKPkg7J%TDfHY%AvfP=D zs447-ZDo%8jvUB(WD^B=JZb@S2i7>a73CzI?1*(vyYHxhA3*N#SZjzsdI6d`An9aB zdG*>+SN4g2bOIEeQVw>cMX=Y7>f&o-pZG^9K+!SfU`M&1rR+ZOk5&NETnSz5NY>TF zKJkxQ0CE;~)F=MY2vAqIN$X-qvYtBji9ZSfR*`2%ccP9i?^CloB z-`m*(c;#QRREgRr`*T0203KC_jCpziQu65}*pc?MREjxj^naN&?iu zw;ko@_Z_iZ5}*lz?C8uk$Rt1uLfcUizyTrc2z`H_1aLqoJ3`+dqwnE7ueXOIwr|MLMB*ZN3i1+zggoo<%o6C(5i$FQ}wg|JaBYh?C zu4s!BGw+Ugi#l=!+jrCn`bm;cz5G$LbNh70#ysG@qXb9f?rTT>n?zG2xBMKEVR2jA}xx+T=Z w*b#11dFChEnFn~M11_ftHz6S*A;A^@13R7!Nr;d#@&Et;07*qoM6N<$f{WsxLjV8( literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/sym_keyboard_clear_clipboard_lxx_dark.png b/app/src/main/res/drawable-xxhdpi/sym_keyboard_clear_clipboard_lxx_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..f9528e85ba98fa7eca88aefa51fbc890c6658d67 GIT binary patch literal 1800 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 XD{sWXXbc3Z00000NkvXXu0mjfi01d8 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_clipboard_pin_holo_dark.png b/app/src/main/res/drawable-xxxhdpi/ic_clipboard_pin_holo_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..e48e9e0afcafe1a08eb353d2f501cfc2f653d1c4 GIT binary patch literal 797 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7xzrV0z-|;uum9_x7$~mb9acTcM+e zLED5h{*Bu^@+VbG^u)Yy%Vc(%9>J99-|@1>Y>>dFd>9 ztYz zgh++0{q@{0vTY^1=GYpqc-|Cv%AetNm4S$5by~oA*24@u2SDi6&IE}DMi6qa73X0# zU|=?A`1<|d$L=ZXH-7v1`E%v6y1Rj4=lu8|Y9+Z}xmHI{9^N{)hUn zK2uR66fJ&<CPvAoVr{#$PWCpU3_?vg@32<+i%LY6{}B m%fI(Med-NLk1Cl}b&MfCdv@&&KF0%0_6(k`elF{r5}E)@V^|#k literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_clipboard_pin_lxx_dark.png b/app/src/main/res/drawable-xxxhdpi/ic_clipboard_pin_lxx_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..767237e8cef2eafffb53cad0db52d742a8fe0c54 GIT binary patch literal 1705 zcmbuA=~ohn8pc6Qqe*QPccq$0L&M!tuKS2Y$7_Vc+&jb5pY#0h@{59;5AdS8C(%+e?*1o`)=|F;d* zr*88;CqSK7>WpVq_ECL&&zHAbS=y$!gxJF2J_LW}VjYiF@zM`b{|8*gissHv2X9Dr ztsnMAa1!Y9BxD0=rxkooz`FMSC^!*R6CQ#VFair>_@-Px91Je=vzHC9tayWdM0PA?iU7Kz^Qkh?Y~e} zhw8H^2~RGUIgY2U42@zcY$+g($G8RTnzvtAlj#p6xh-aPeb=Z=H?FPm+gzdkts6K` znzx-4rgr)&PcW$_pCS9HifDR78-VhpIc(X`AX;}vHG@ZhL;#cs82E>{>(Dp9DdTw11>?ZG z`5Y$zrxQAZ=!z=U*M~NJhuGNgDv<~m$%?CraE0rYENlaQ2f*&@gaXB_;3-N2X<2>H zx?3py^<|pqm4|w*cD+-5ZG^SY!xomgP0-N&W%ys) zqa|b5kkCa;{2Xn4iIib?U-NhZq8?^R2-rCruugy8KQP#T5aYM%4=(&rSV+w zCi}6;#p2Qd_SA-0@9(qZy(Egv6XvG(t@8gP`3rrATJi-J8^=<~)xz^{b}%(oikpxz8yfi}1~C#*^eOFRV}(|^Zn!~U?}w%- z@+LNMXP_oA2%*EtOmaGv9aeOU@l%_X%OQkImUK4H>v_mWOk@sE>f(dv(*l^{4YzmO z^Yy3m;Ky=qwihTjn&V%LeJ(S*=a_l8)FEAtWkMvH@VqK+)F-ozW^Rm2ThOxZ;lm(Y z-t;5?_}MxDhrH_U5X{t`OZSvx+af!ytkLFcCd$A`H9_`C#e?pi8Z97-K~5dHuAO{V z6*j3icaT@UI>y{3HtSatEd5CzXx|zpZ#`k4{KA5Thmj!W!q~!P**Ih zZ7(+%IKW-C5a||B_B|iJZs`FKcD+_W=>yHI-9=cmaz12=zMR3=t!~C~ueA&EV)gjp zms2H?Rn*_H539|cc}wiyfNIjb0_q{>(jcF_eCTzWx$o#TvuXq9BuzErf)gF01)sd) zisf%84ZsV#Ay)N6_$Cj%7%Q4QGzDmEITp1Axvt2yPj zub1Dh^ERiBEI3s~+CH6a6WBR-lNvdt(zDv+N>`(& z1es0qPf1kN-XBLSyb)bWG38KCsLqkOlf>JicX}Bfyg|ao*|Uo|C%mo?MUTfkJhyvE z{>cR?!<}#<49$ZM`p7oJHG|kyqRha~e;FwB)W z5mCHuy}t(ab1meIgYj3ITshR*-7BzjHO>{Uy$GdTTOP6yOpY8_N}G4G1LvnSDl3aJ zUi58wm#utPL5{BTdiH~%y@pbY;Ms3~u-~5j`=%E4V+zy|%J$iNK>#!|(67ZeHurx< CA21F8 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_clipboard_pin_lxx_light.png b/app/src/main/res/drawable-xxxhdpi/ic_clipboard_pin_lxx_light.png new file mode 100644 index 0000000000000000000000000000000000000000..b090700a5f605e669d470c180d7e8119d9a30bed GIT binary patch literal 1579 zcmbuAi96E`0LOnzG31I=TOwssM9e6XqoO&c2<6&P$kCw#v&}~8ox6i+^+iTxw6 z^lu-}jyeJWTNY6#ZO%pe{dhWhwckVSZQxrCBdhJhr9Yqzy{WKyhUnNTGbwt?zYp67 zGf^SxN6>1}ZbthvL`qIt&b)RJ+==$4zj+(yqSe$Ssw+_F{j(Id zF1i@))fZSey-x$Fl)7j+9Xl&*HoJ{K9p8*)AZDd%D5ol_>}8WmAn%avAqzl6fI>_Cb+L(vHqnSPvrqLmQIDu7uVW;?l4G zs6uGEv^BCYu~o}bABFqhxvuC`2S#$`L0%Vd2E_!CceR!tG1xE1=22#@issiT-`Wtb z`qr4p5pdad6X-(Q6;|70YOr!$D8$hPz4+XhR`5>BB;oeIt(H{A!iJj3#E%0ZpOPzT z9TuS)^t(s{X7l@u1caf>nCFV|(oIeM6IW>;mtI_+gr7HZT{$okfpxmn9w*5RBP zkmAWf9tN5TL*|Uw-`;Vw%S{97Xepw5`1YUk72F()ZRX4TfTE&folCDRG?O&VCHzf? za@ir%(8?FhDyqeo_R$}B4ilUo3=lxogmG{<(J59yRWGWl^bz3v@Eq3x_e^`0Vi1KX z(i{c^c-V2z?CvXnn@u%ie+ zgz20`d9#d6$s*X;zlt~0coUy~UcdYq&_hauSd8)EK!_}spGS(bmz5S_qaTcUh zWmYz+vaImN&QF*Wrmg^I91Md8AbBnCjTBIw!?q{BQ`dz%a?%fny)4j$YUDoOU?$(c zdp@YO;7)O@8kn40@l7lQf9t|bw#W5e*jfS{!XIpq<5}msMY(ISXj(;MIE2w{$}O~E zE?=p^gK_RPa#ut?)h55fy?Br69g?;1FG zE&{cxbKle^cjJB1b@jB54ZZO+2eocfly+I#&GDYYT%4(I)JF<8{mg6h8*+3js|*w6 zjF?9oiBZdq#J`Qe^+QMN`O3%fbpkk%ke+Hb0-4XQ#Hr*f`Jp#PaqcN9oF_slhc!MPEYY;a9`uU zOgh|aOfP2x7g@kXKQq9f>9B-q;gD42aAeetKA!>#^wEEMEnapzIJgaCcwBEqVw{+n zVDI(zGd#j4j4?a$+NQsAd0vMPP7jaSN1dEHK3+bEFUv0XP=Aa(+9^-({3VG>$u7dX zya+5|XJY&vhLIR?vPIRN*5x!_pXOQb)y|#k_jy^3zgE1~+(r_y%*BHpn4p=r?YPjj zjN35Pvw@R?Kz(A*=>5H(o|~BBEMMgbEVsSrE4W@OA6?m4 zyrcb!F3t>UcK7Hdts>U1Qa<1cQe=0Lw*GIr;*i*7Zj0qb>n$W`KvuG0-xf0;d4-9S%!QzAhy z-Bh7n_-BicNQs)0u9}slnQzp~Wk*y*AE9NA@Z>Ai#T0VgkrPaeCZsQ{FzR3_n+0pO z3+=U{xHAiSirEZ%<`dkzXI6qjiI_Kw4>&>g?ua6+<^c b>JnhSLQU(|>9#Y!PXIujLZ2+N#m4^wv&rz6 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/sym_keyboard_clear_clipboard_holo_dark.png b/app/src/main/res/drawable-xxxhdpi/sym_keyboard_clear_clipboard_holo_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..c384620861309767b2cbf5404721b3bda5a1e8bf GIT binary patch literal 2080 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/sym_keyboard_clear_clipboard_lxx_dark.png b/app/src/main/res/drawable-xxxhdpi/sym_keyboard_clear_clipboard_lxx_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..107356757eb363660fd6db015e4e07c784519d06 GIT binary patch literal 2156 zcmV-y2$T1TP)&3=7A;y} zmLv(WT0l07=Gsu?u{2GeT3A@v-0gO^1G_Yy=P{mR8`&?~kj5(<#odY`zlvgirj+^` z_zCz`<1~-)9NVxh`$#{*+H&6FDCT+;^#F={21ULC`~%zq{sr!6+}6i_O0)v?Z#z#d$9tW{EBkqSN>a{|_?+yUlu)X#Kl4tY962X}5&_?v5aTQ0rdlN51fF9)#GrP3Kz|3{&ox?x zkQ`?m@2N%Ek%J*}60Gh7J%ic;aB5fz_?y79<{)BSLFoc0~#nt@VC0t>FnUN5O@GQ1|F3Ge(!7lGG?*B=g{^z z@QMZ>*5^bU3HrP*?``G@g4Yl_=eKxnKz|u$7%&&UcYKT3dy&H$IriGdg2L$$we%ru z0u#A{wr`-%I_W1jCWkhYDkFh&^fI6Qz*u*1)x8UUbobKVVH@`@c}+eUvuopT}@+FWc1cJq#F&B%GieIejLi z*^8L199=@&cLtEaJK=!JNT_osTt|`jln}6oZEE-(794*E@n`v9mbndO737HLE?Uab zl&UUAYFYQ8ptK; zaUSD2-A48s1@3WngnYKj@j9`B9OW|rnZW;vDF1Tw3kLfsa9HCL9y7G73f?_@13%vf zJjXV&A3*{hc_V;^qVXfhLk2S>P=s$eBK}s{@-)H!m*?w%1wMo^P63kw%=3_LBl{8l z37~TZVK74i5j~&3sG2KBD}sPL={j;G{rCXmaFsP^ z0=Oo^Z@4nKC5ftPa`Xdm5x6`8aM^rkhy>)xuuz`^e;C?G5^Us%BnWC3p6dLnlkj|WYF9>6qGnd@N3(2N&dSzMxA4j1e6Ux!b*

2OQgF7j_J0lmQ~J2|Sml|~D2 zRa)=_&^R!jD?{pd?Z_skfOOF_o(rA;TT2_bl)a(|py@lIF-zGijsTi#IU2@RBmuOh z$Wbc@NUA>&d*CVA8=e{?+LQ-OA4Vp z$Wa>zD87glUw~}E7p048@gHTE!MPl@g@AkjWlIsR;8c#;Qnn^w zm&!hGNW87Ej%-Tc%&9xkPA9vdbpt+VKuVzWW6g80~gn}mQJF>E*KLI)N zU;D_d-cRi9X)@F@?>n+G3jzlH;?pK$S@#`TnI?b<_9I{=Vf1nY0yctxlZ6CiQpMy# zg&a#6Mk_}kV62<{z6buQ;CL5g7@Zs?*++>91pG#2T3PjqX)9QV(a2Gnt$pwhC7e|m z3A_R#5u=nN&hs@1C)DD!jR~w2$l@1C7>yk1JAr@qYnBgY&W<=mZ8^N{JE~=TCh)<` z*%7CxZAa>H-%&J6ORKFNT_y=2ulaw&G9*g}FJKU4mUc2giAn8nFpOp{=eqhj%2 z;ImTpjpoPb0H7;ZZslo_oQ1s1RYwH=h9B%LXg($dbf(;8%Df(sW)qlx+l*h->t>kp6x?hH#3 zV+bb5!abU3FwjCLvXBnJ_+|VH)!=FUyGaXe;Pded4ul348U($jB&q`L}TJDxCc% z{{9X8Gcz;ul;CGxGtyR>mt|O1tWP`1=kxugQfUZ(^J1E6K~Ek$c<|u+aO^T1`^0ee ze*`|az;c~=p0LcJLx(;@9b3hEwGsII*~5nqpPZeY{T_cm!2bhc9#w+2woOb-oSvJT z`^CxOeqb4vh2E!8*LJb~$P1LtjgOC?L{D0xF(6^>$dM!OkQ6bmY(cMm4sCymc7JWh z`BoC3|8E!xpF!_Iv7N{YosXV#3{JmkI)Hx{78X`P!rLlG0-T?Q!)iv@u{kv?3=)=M z&ugeVBeoYgfszC4`Bvv!jlr1ym?XS7DZ|Lw8Iy|V=YWE1_ zf52;EK0)W(u&s;^woPoeO$-3(R4OH!+`gc~O&F@41egX{!jUTpsIM2RnBCLU(_g~@ zw}88Tn4h2bjQeyna19%<(^FGZlLS70#A|*eux_!vc7VFpu+%*V94{0KJFwc7x94DJ z!Rlji)*%Fm1|b}+BJX>gGsJpKL0SqVj3NI85X(?t3m+oHwybTUj;pBe4NlY`I7RR? zuOa=aUwK)89*;5l?!ZrHQTJZhDQ~ZY%JP^8UJpV76uE(+y#Oa{66-MqX?@tC4x`LP zlLWCYKd=l4zmL&$0`=Jl=QWIm6L8A?cIpoc3lR@7vM$0tk|1>?v@AnK1+0p|=<5hw zHTb}F=ZUmll7RddDvh12tu8MZ#{Pqx?cBVZYM=TSs@I5Ld<0~0^z99#XohDq^V<_TJ-EEu*w=ETg^k*Hh8VuYhb_Hr-d zn5HruF(2lE>*#6wp;P_nnc!z$V_MLE>T5$rHC1h;9$=l#x#}T zsM6R2`~Zia!vFIGKl2*X#QY)gFR%hEyMSrTBbGBq=v`$5P(Z@YATieH4JyMC_X6w) zx2laz+wT&b&+;pn$QRJj3c#>{0@Iks$lFrBD+I7|V{v$LmhzD!z*I_QIFbyLfMK^)76=&9iU3nFDy=FUNklve zFqKmojwB(z1egj&JRIpy0I$2HS<=IXBXcB}>u^=zJ?ABD2{4??rxUm&w540~g$YL> z;473_0l3^$i50Z<-PF|7i&_#urDz1wKLJ);@-&qV5{@uR`mo8`gZ$GdS5=G(+L|a9 zi<_P9nrT4*u!POZ2oI%ZC7m5*vstdg>qidVfRpnn8BGY_BsR2V%a&(k9%V;CbRwWW ziOD|7UT@Kk0Opr{l)av!B>@!UC|mn*q%8pyOE|jWQnqPCRKts!U>iZgQNE4mj$FWv+v9Ukd`T45a}}GmW!%=C{G6?xvA#h9g@$4$%94 zfT5Hp0dU?Pl)X$tnODFPjRWqv)Za8TL^v8n{_`kz&7nOJZ2vOs)AxP=4RIp?4(VB3 zTujqI=5Ye|2{Mne(Pj!n?Lyr{Vp~q2Zq$*wilRLM6dJ}fFUUSS66^Palk+J|)1H8! z62+tJ4MhMYYDd`{jsS}6aMX!xNCGHP2}iLYAjl-_1m0sdR!1u?Z8x1j2dM~0)I)0m z%w;I|0^E?%m@q)^-Vc0Sk@VbASkayUUZeBj#LFnpn9faC7_bb(5Fh>$+qOAFb~uU! z0YQ!;{NU2I4a`Sp$qGlYAi&E26`-E7!jYf;2>~vkt}?=rpZ*B}9*_}^{Pa%<@Ij~H z$WMQr2;j3p&EI}g0SDchPHHzC#esl6gaF>D`VmgBJr*JZthW~6d(3oV*l?sh0ThzJ zI#dA;-8nAt8~s+v#DRcrh^n)rgn$sD;wal7;N^O+GCi0)JI6QZh z5a1xP!x7R_#*Kd4b4LjQE+RV|f9@zDz)uu}qn5`YfrJ1*Q5lX< z=c9yx0HQJ+!8f<~O&u|hG>HQN6cyo!UlQRrbzr-?_Z7s20E&Welq#3YyOr7=Cjuz4 z!;x~k<3<2Qb~sWh0SR#Y(Yq)k3eOz{l8iqA^`Cv5oSZE1&mT>k-vtrEkr{UQK6{C;Sxv-Ku+qt6?Z1fS w_%}(|Str3lCks69_Ur43B$7xXi6kP*|1cg>M4Bnr7ytkO07*qoM6N<$f<}H^Qvd(} literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/sym_keyboard_clipboard_holo_dark.png b/app/src/main/res/drawable-xxxhdpi/sym_keyboard_clipboard_holo_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..deb2af656de290c21ae94eb63403d0e6b0c0780b GIT binary patch literal 1718 zcmZ`)c~sI17XG0Qqa`(n7D{euZ+I@R(6DF$HOS>TE)*`Af?`?ZQlm*GTk6wtA(_d_ zmzqyQ3wequQ)Wf#AeNR|gxeUtBFzjpj6__<=FB-W@64M&?sw1q{`&5B&$-{F;6NY9 zI;(X606_eF@x-;x`C7){HEhSmbO8XE>4*0^d@fpD78Qi>G0(BjMjk#{i+n6_N(#>A zN6%OKk;Gv)`+(Y`7fI(*DcLji+&Q?Wud1iHq&2R)3d}HC9x3 z7m8)WgCFhtY2x>aQHNKg5c8Yksn+w#9(a2=*W`F_ls!S19wfY^S9D)i415oJzG^jp z?1G@*GUI7Ock59WtB)#-=@FihM8IUuJDEJeyAF^3>F$Xhhw~TFANQUb$c;^Vt*n`i z?w%bS+ZUh2#`ZY~y_83)R$PwXJFV=@{g~H$EVDeeaIg_n*{VOdAW z9q?z$tTZWoZxY$O>-xW>l$hA)cjJ9To{bjznqE6))8C{RY>7$vjaddVc0`mF|9pXe zf;Cwms{4}ePmzp55C^d~(A#OsfFSC#x|MN7fn>T5|K_`~_hiZUWOeE4=_K1ABfi)f zv#>ff+&M4B_r+c;dq}!L#=aaxjaT0cz=3C{Q=(6~Z62??RO1@rP771}PlZI1r=wfj zE|xW(T%L*y(2cp6^=OjA-NR9Iu48zMGuqVRjFJ zRc$wPyam^F0VdxbTygXC{w*4j$e~L=?JCeHRey?L+?tHaT59wF$aA}!X6vgPr)QHh zqj|7q9b^9>0zk?~we@KUc~?Db!ET;!I)M@g!#J*K5ys7P?7xO53ZZfLqnIO@FG9$Z z>rdnUyr)nqB!U|ZVROFPM6 zUtiSRHIUh_X?7LlG_e~S;0N{aVpuQV@>+>NaBvtIT^qFf_<>aKK-HM$b6he|5nM2` z!!;4R!z05`Rq#LJ{;f2jSo2LIR7GVYyGz6%uu0KDCwzUc%S4Q|bJ*Y|~zrp_R(^ z{Ze5OXeik;YpJSK0(JA$LrxU2uV{8{y}Pz|h7ggjsaAtKfvmX4HZ1CyIktL8gV~q; z2?7*Qr;Ow@O^ltPs__5m{tdf-<-O-v!d=jdoc3Sb&8}?9w+eBlg&vZ{K{y8;b6WU& z2^t^tXU)e#&@&3nKnSb5dbO+n=Ri2;GM}ErfwszxkWvdU{A?m8YW>^J%w~Z}a9*u5 zEodWytjH-_>Wg#3h$tODOc_j?i){{NoFCQS4NZ9cAU<$ky$LZ7NefgnaSATx^l<6@ zWUr{}NsJ2OWM#)@dFhazm70m+;nZoq6njtGCOKCyW=l*_<_0!!(#8WkMMq#wHG-=o zO-tm$2;n~LQ!W~`{y_N#LE+X3*4j6r`6p)3-)Ed7$DV0hS1dCxCk=`y{r)sZ+Ceq< z_BOQodB%#<%(*sqCxpezEIg!zDTjF*+kef4{{YWw)xew^&#m}wQ}J3J0e*x)e4RHX F^PgQ(IRXFx literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/sym_keyboard_clipboard_lxx_dark.png b/app/src/main/res/drawable-xxxhdpi/sym_keyboard_clipboard_lxx_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..22f8c6d3814045c0c89af7765cd554e452581b5a GIT binary patch literal 1544 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/layout/clipboard_entry_key.xml b/app/src/main/res/layout/clipboard_entry_key.xml new file mode 100644 index 000000000..d6a4cb8e2 --- /dev/null +++ b/app/src/main/res/layout/clipboard_entry_key.xml @@ -0,0 +1,30 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/clipboard_history_view.xml b/app/src/main/res/layout/clipboard_history_view.xml new file mode 100644 index 000000000..980642f03 --- /dev/null +++ b/app/src/main/res/layout/clipboard_history_view.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/input_view.xml b/app/src/main/res/layout/input_view.xml index 404cc924b..e191c6ab1 100644 --- a/app/src/main/res/layout/input_view.xml +++ b/app/src/main/res/layout/input_view.xml @@ -29,4 +29,7 @@ + diff --git a/app/src/main/res/layout/suggestions_strip.xml b/app/src/main/res/layout/suggestions_strip.xml index 7f98671b5..d5a4cfbc9 100644 --- a/app/src/main/res/layout/suggestions_strip.xml +++ b/app/src/main/res/layout/suggestions_strip.xml @@ -58,7 +58,6 @@ android:layout_width="@dimen/config_suggestions_strip_edge_key_width" android:scaleType="fitCenter" android:layout_height="fill_parent" - android:contentDescription="@string/spoken_description_mic" style="?attr/suggestionWordStyle" /> diff --git a/app/src/main/res/values-land/config.xml b/app/src/main/res/values-land/config.xml index 22b811329..d6d8d4d93 100644 --- a/app/src/main/res/values-land/config.xml +++ b/app/src/main/res/values-land/config.xml @@ -82,4 +82,7 @@ 70%p 70%p 32 + + + 3 diff --git a/app/src/main/res/values-sw600dp-land/config.xml b/app/src/main/res/values-sw600dp-land/config.xml index 4410c191b..8be20d477 100644 --- a/app/src/main/res/values-sw600dp-land/config.xml +++ b/app/src/main/res/values-sw600dp-land/config.xml @@ -70,4 +70,7 @@ 64%p 64%p 36 + + + 4 diff --git a/app/src/main/res/values-sw600dp/config.xml b/app/src/main/res/values-sw600dp/config.xml index 860a3bcf0..4283ccb2a 100644 --- a/app/src/main/res/values-sw600dp/config.xml +++ b/app/src/main/res/values-sw600dp/config.xml @@ -87,4 +87,7 @@ 78%p 78%p 36 + + + 3 diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 7defaf7dc..db069751e 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -30,6 +30,8 @@ + + @@ -205,6 +207,11 @@ + + + + + 2dp + + 1.5dp + 18dp + 8dp diff --git a/app/src/main/res/values/config.xml b/app/src/main/res/values/config.xml index 5dc7ea951..1e8a6959a 100644 --- a/app/src/main/res/values/config.xml +++ b/app/src/main/res/values/config.xml @@ -93,6 +93,9 @@ 78%p 32 + + 2 + + + @@ -65,6 +66,14 @@ @drawable/btn_keyboard_key_lxx_dark_amoled + + @@ -65,6 +66,14 @@ @drawable/btn_keyboard_key_lxx_dark_border + + + @@ -62,6 +63,14 @@ @drawable/btn_keyboard_key_lxx_light_border + + +