diff --git a/app/build.gradle b/app/build.gradle index 33e8ae96..f188af09 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 00000000..aba0ff87 --- /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 96961a76..8be6c628 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 633a3e52..ba365d20 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 00000000..41ae0dfa --- /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 00000000..507b2293 --- /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 00000000..819942dc --- /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 00000000..e407a5cd --- /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 00000000..5b1b2a9a --- /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 58f5b841..9218b2e0 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 5e2430a4..d97223b3 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 8d8f7d84..4b5177a2 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 88035ea2..957cc798 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 00000000..5f071907 --- /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 00000000..514af5f8 --- /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 1285380d..b104e64c 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 eb75d987..7311d8b5 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 5af4e72a..31bcc6bf 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 e2c1a2e0..875dbdd3 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 f084ac39..6851794a 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 00000000..e23aa349 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_clipboard_pin_holo_dark.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_clipboard_pin_lxx_dark.png b/app/src/main/res/drawable-hdpi/ic_clipboard_pin_lxx_dark.png new file mode 100644 index 00000000..708249eb Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_clipboard_pin_lxx_dark.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_clipboard_pin_lxx_light.png b/app/src/main/res/drawable-hdpi/ic_clipboard_pin_lxx_light.png new file mode 100644 index 00000000..b719e1a1 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_clipboard_pin_lxx_light.png differ 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 00000000..138984ca Binary files /dev/null and b/app/src/main/res/drawable-hdpi/sym_keyboard_clear_clipboard_holo_dark.png differ diff --git a/app/src/main/res/drawable-hdpi/sym_keyboard_clear_clipboard_lxx_dark.png b/app/src/main/res/drawable-hdpi/sym_keyboard_clear_clipboard_lxx_dark.png new file mode 100644 index 00000000..c7cec6bd Binary files /dev/null and b/app/src/main/res/drawable-hdpi/sym_keyboard_clear_clipboard_lxx_dark.png differ 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 00000000..9b16dc28 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/sym_keyboard_clear_clipboard_lxx_light.png differ diff --git a/app/src/main/res/drawable-hdpi/sym_keyboard_clipboard_holo_dark.png b/app/src/main/res/drawable-hdpi/sym_keyboard_clipboard_holo_dark.png new file mode 100644 index 00000000..2b68eaee Binary files /dev/null and b/app/src/main/res/drawable-hdpi/sym_keyboard_clipboard_holo_dark.png differ diff --git a/app/src/main/res/drawable-hdpi/sym_keyboard_clipboard_lxx_dark.png b/app/src/main/res/drawable-hdpi/sym_keyboard_clipboard_lxx_dark.png new file mode 100644 index 00000000..fb7be8b2 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/sym_keyboard_clipboard_lxx_dark.png differ 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 00000000..318f4407 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/sym_keyboard_clipboard_lxx_light.png differ 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 00000000..74d1ede9 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_clipboard_pin_holo_dark.png differ 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 00000000..838e6f4c Binary files /dev/null and b/app/src/main/res/drawable-mdpi/sym_keyboard_clear_clipboard_holo_dark.png differ 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 00000000..5cdbfd52 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/sym_keyboard_clipboard_holo_dark.png differ 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 00000000..191d7449 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_clipboard_pin_holo_dark.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_clipboard_pin_lxx_dark.png b/app/src/main/res/drawable-xhdpi/ic_clipboard_pin_lxx_dark.png new file mode 100644 index 00000000..ae5cd606 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_clipboard_pin_lxx_dark.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_clipboard_pin_lxx_light.png b/app/src/main/res/drawable-xhdpi/ic_clipboard_pin_lxx_light.png new file mode 100644 index 00000000..515da729 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_clipboard_pin_lxx_light.png differ diff --git a/app/src/main/res/drawable-xhdpi/sym_keyboard_clear_clipboard_holo_dark.png b/app/src/main/res/drawable-xhdpi/sym_keyboard_clear_clipboard_holo_dark.png new file mode 100644 index 00000000..fd193945 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/sym_keyboard_clear_clipboard_holo_dark.png differ diff --git a/app/src/main/res/drawable-xhdpi/sym_keyboard_clear_clipboard_lxx_dark.png b/app/src/main/res/drawable-xhdpi/sym_keyboard_clear_clipboard_lxx_dark.png new file mode 100644 index 00000000..7c7076bf Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/sym_keyboard_clear_clipboard_lxx_dark.png differ diff --git a/app/src/main/res/drawable-xhdpi/sym_keyboard_clear_clipboard_lxx_light.png b/app/src/main/res/drawable-xhdpi/sym_keyboard_clear_clipboard_lxx_light.png new file mode 100644 index 00000000..eb2804ab Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/sym_keyboard_clear_clipboard_lxx_light.png differ 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 00000000..595577a2 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/sym_keyboard_clipboard_holo_dark.png differ 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 00000000..fd3d22c2 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/sym_keyboard_clipboard_lxx_dark.png differ diff --git a/app/src/main/res/drawable-xhdpi/sym_keyboard_clipboard_lxx_light.png b/app/src/main/res/drawable-xhdpi/sym_keyboard_clipboard_lxx_light.png new file mode 100644 index 00000000..bc17c65a Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/sym_keyboard_clipboard_lxx_light.png differ 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 00000000..b53f4ba9 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_clipboard_pin_holo_dark.png differ 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 00000000..ba419856 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_clipboard_pin_lxx_dark.png differ 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 00000000..e8a83751 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_clipboard_pin_lxx_light.png differ 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 00000000..66f56395 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/sym_keyboard_clear_clipboard_holo_dark.png differ 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 00000000..f9528e85 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/sym_keyboard_clear_clipboard_lxx_dark.png differ diff --git a/app/src/main/res/drawable-xxhdpi/sym_keyboard_clear_clipboard_lxx_light.png b/app/src/main/res/drawable-xxhdpi/sym_keyboard_clear_clipboard_lxx_light.png new file mode 100644 index 00000000..31d83ed1 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/sym_keyboard_clear_clipboard_lxx_light.png differ diff --git a/app/src/main/res/drawable-xxhdpi/sym_keyboard_clipboard_holo_dark.png b/app/src/main/res/drawable-xxhdpi/sym_keyboard_clipboard_holo_dark.png new file mode 100644 index 00000000..a3d03a7b Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/sym_keyboard_clipboard_holo_dark.png differ diff --git a/app/src/main/res/drawable-xxhdpi/sym_keyboard_clipboard_lxx_dark.png b/app/src/main/res/drawable-xxhdpi/sym_keyboard_clipboard_lxx_dark.png new file mode 100644 index 00000000..954d86a4 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/sym_keyboard_clipboard_lxx_dark.png differ diff --git a/app/src/main/res/drawable-xxhdpi/sym_keyboard_clipboard_lxx_light.png b/app/src/main/res/drawable-xxhdpi/sym_keyboard_clipboard_lxx_light.png new file mode 100644 index 00000000..7a563963 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/sym_keyboard_clipboard_lxx_light.png differ 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 00000000..e48e9e0a Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_clipboard_pin_holo_dark.png differ 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 00000000..767237e8 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_clipboard_pin_lxx_dark.png differ 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 00000000..b090700a Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_clipboard_pin_lxx_light.png differ 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 00000000..c3846208 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/sym_keyboard_clear_clipboard_holo_dark.png differ 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 00000000..10735675 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/sym_keyboard_clear_clipboard_lxx_dark.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/sym_keyboard_clear_clipboard_lxx_light.png b/app/src/main/res/drawable-xxxhdpi/sym_keyboard_clear_clipboard_lxx_light.png new file mode 100644 index 00000000..86567cc1 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/sym_keyboard_clear_clipboard_lxx_light.png differ 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 00000000..deb2af65 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/sym_keyboard_clipboard_holo_dark.png differ 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 00000000..22f8c6d3 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/sym_keyboard_clipboard_lxx_dark.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/sym_keyboard_clipboard_lxx_light.png b/app/src/main/res/drawable-xxxhdpi/sym_keyboard_clipboard_lxx_light.png new file mode 100644 index 00000000..32de6408 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/sym_keyboard_clipboard_lxx_light.png differ 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 00000000..d6a4cb8e --- /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 00000000..980642f0 --- /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 404cc924..e191c6ab 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 7f98671b..d5a4cfbc 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 22b81132..d6d8d4d9 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 4410c191..8be20d47 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 860a3bcf..4283ccb2 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 7defaf7d..db069751 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 5dc7ea95..1e8a6959 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 + + +