Implemented clipboard history feature

This commit is contained in:
pdroidandroid@gmail.com 2022-01-13 13:45:48 +01:00
parent ac036f277e
commit 1cbbf4983c
82 changed files with 1128 additions and 72 deletions

View file

@ -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'

View file

@ -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("", ""));
}
}
}

View file

@ -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;
}
}

View file

@ -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;
}

View file

@ -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<ClipboardAdapter.ViewHolder>() {
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<ImageView>(R.id.clipboard_entry_pinned_icon).apply {
visibility = View.GONE
setImageResource(pinnedIconResId)
}
contentView = view.findViewById<TextView>(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
}
}
}

View file

@ -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
)
}
}
}

View file

@ -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<ClipboardHistoryRecyclerView>(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<FrameLayout>(R.id.clipboard_action_bar)?.apply {
clipboardLayoutParams.setActionBarProperties(this)
}
alphabetKey = findViewById<TextView>(R.id.clipboard_keyboard_alphabet).apply {
tag = Constants.CODE_ALPHA_FROM_CLIPBOARD
setBackgroundResource(functionalKeyBackgroundId)
setOnTouchListener(this@ClipboardHistoryView)
setOnClickListener(this@ClipboardHistoryView)
}
clearKey = findViewById<ImageButton>(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)
}
}

View file

@ -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
}

View file

@ -0,0 +1,9 @@
package org.dslul.openboard.inputmethod.keyboard.clipboard
interface OnKeyEventListener {
fun onKeyDown(clipId: Long)
fun onKeyUp(clipId: Long)
}

View file

@ -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 {

View file

@ -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;

View file

@ -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

View file

@ -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 */

View file

@ -0,0 +1,13 @@
package org.dslul.openboard.inputmethod.latin
data class ClipboardHistoryEntry (
var id: Long,
val content: CharSequence,
var isPinned: Boolean = false
) : Comparable<ClipboardHistoryEntry> {
override fun compareTo(other: ClipboardHistoryEntry): Int {
val result = other.isPinned.compareTo(isPinned)
return if (result != 0) result else other.id.compareTo(id)
}
}

View file

@ -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<ClipboardHistoryEntry>
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<ClipboardHistoryEntry>) {
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<ClipboardHistoryEntry>()
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<ClipboardHistoryEntry>) {
// 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()
}
}

View file

@ -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<LatinIME> {
@ -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<ClipboardHistoryEntry> entries = (List<ClipboardHistoryEntry>) 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<ClipboardHistoryEntry> 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);

View file

@ -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";

View file

@ -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());

View file

@ -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;
}

View file

@ -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<ClipboardHistoryEntry> jsonBytesToHistoryEntryList(final byte[] bytes) {
final ArrayList<ClipboardHistoryEntry> 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<ClipboardHistoryEntry> 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) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 566 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 658 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 658 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 978 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 751 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 611 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 728 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 414 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 777 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 522 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 573 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 955 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 960 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 936 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 851 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 975 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 835 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 797 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:id="@+id/clipboard_entry_pinned_icon"
android:layout_width="@dimen/config_clipboard_pinned_icon_size"
android:layout_height="@dimen/config_clipboard_pinned_icon_size"
android:layout_marginVertical="8dp"
android:layout_marginStart="4dp"/>
<!-- Provide audio and haptic feedback by ourselves based on the keyboard settings.
We just need to ignore the system's audio and haptic feedback settings. -->
<TextView
android:id="@+id/clipboard_entry_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginTop="4dp"
android:layout_marginBottom="6dp"
android:layout_marginHorizontal="8dp"
android:hapticFeedbackEnabled="false"
android:soundEffectsEnabled="false"
android:ellipsize="end"
android:maxLines="4"/>
</LinearLayout>

View file

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="utf-8"?>
<org.dslul.openboard.inputmethod.keyboard.clipboard.ClipboardHistoryView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:orientation="vertical"
style="?attr/clipboardHistoryViewStyle">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/clipboard_empty_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="¯\\_(ツ)_/¯"/>
<org.dslul.openboard.inputmethod.keyboard.clipboard.ClipboardHistoryRecyclerView
android:id="@+id/clipboard_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"/>
</FrameLayout>
<FrameLayout
android:id="@+id/clipboard_action_bar"
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1" >
<!-- TODO: Implement a KeyView and replace this. -->
<!-- Provide audio and haptic feedback by ourselves based on the keyboard settings.
We just need to ignore the system's audio and haptic feedback settings. -->
<TextView
android:id="@+id/clipboard_keyboard_alphabet"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginStart="2dp"
android:layout_marginTop="1dp"
android:layout_marginEnd="1dp"
android:layout_gravity="start|center_vertical"
android:paddingHorizontal="12dp"
android:gravity="center"
android:hapticFeedbackEnabled="false"
android:soundEffectsEnabled="false" />
<ImageButton
android:id="@+id/clipboard_clear"
android:layout_width="@dimen/config_suggestions_strip_edge_key_width"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
android:layout_marginEnd="2dp"
android:padding="8dp"
android:hapticFeedbackEnabled="false"
android:soundEffectsEnabled="false"
android:scaleType="fitCenter"
style="?attr/suggestionWordStyle" />
</FrameLayout>
</org.dslul.openboard.inputmethod.keyboard.clipboard.ClipboardHistoryView>

View file

@ -29,4 +29,7 @@
<include
android:id="@+id/emoji_palettes_view"
layout="@layout/emoji_palettes_view" />
<include
android:id="@+id/clipboard_history_view"
layout="@layout/clipboard_history_view" />
</org.dslul.openboard.inputmethod.latin.InputView>

View file

@ -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" />
</LinearLayout>
</merge>

View file

@ -82,4 +82,7 @@
<fraction name="config_emoji_keyboard_key_letter_size">70%p</fraction>
<fraction name="config_emoji_keyboard_key_label_size">70%p</fraction>
<integer name="config_emoji_keyboard_max_recents_key_count">32</integer>
<!-- Clipboard keyboard -->
<integer name="config_clipboard_keyboard_col_count">3</integer>
</resources>

View file

@ -70,4 +70,7 @@
<fraction name="config_emoji_keyboard_key_letter_size">64%p</fraction>
<fraction name="config_emoji_keyboard_key_label_size">64%p</fraction>
<integer name="config_emoji_keyboard_max_recents_key_count">36</integer>
<!-- Clipboard keyboard -->
<integer name="config_clipboard_keyboard_col_count">4</integer>
</resources>

View file

@ -87,4 +87,7 @@
<fraction name="config_emoji_keyboard_key_letter_size">78%p</fraction>
<fraction name="config_emoji_keyboard_key_label_size">78%p</fraction>
<integer name="config_emoji_keyboard_max_recents_key_count">36</integer>
<!-- Clipboard keyboard -->
<integer name="config_clipboard_keyboard_col_count">3</integer>
</resources>

View file

@ -30,6 +30,8 @@
<attr name="mainKeyboardViewStyle" format="reference" />
<!-- EmojiPalettesView style -->
<attr name="emojiPalettesViewStyle" format="reference" />
<!-- ClipboardHistoryView style -->
<attr name="clipboardHistoryViewStyle" format="reference" />
<!-- MoreKeysKeyboard style -->
<attr name="moreKeysKeyboardStyle" format="reference" />
<!-- MoreKeysKeyboardView style -->
@ -205,6 +207,11 @@
<attr name="iconEmojiCategory10Tab" format="reference" />
</declare-styleable>
<declare-styleable name="ClipboardHistoryView">
<attr name="dividerBackground" format="color" />
<attr name="iconPinnedClip" format="reference" />
</declare-styleable>
<declare-styleable name="SuggestionStripView">
<attr name="suggestionStripOptions" format="integer">
<!-- This should be aligned with
@ -270,7 +277,6 @@
<attr name="iconPreviousKey" format="reference" />
<attr name="iconTabKey" format="reference" />
<attr name="iconShortcutKey" format="reference" />
<attr name="iconClipboardKey" format="reference" />
<attr name="iconIncognitoKey" format="reference" />
<attr name="iconSpaceKeyForNumberLayout" format="reference" />
<attr name="iconShiftKeyShifted" format="reference" />
@ -281,6 +287,9 @@
<attr name="iconImeKey" format="reference" />
<attr name="iconEmojiActionKey" format="reference" />
<attr name="iconEmojiNormalKey" format="reference" />
<attr name="iconClipboardActionKey" format="reference" />
<attr name="iconClipboardNormalKey" format="reference" />
<attr name="iconClearClipboardKey" format="reference" />
</declare-styleable>
<declare-styleable name="Keyboard_GridRows">

View file

@ -136,6 +136,10 @@
<!-- Common configuration of Emoji keyboard -->
<dimen name="config_emoji_category_page_id_height">2dp</dimen>
<!-- Common configuration of clipboard keyboard -->
<dimen name="config_clipboard_divider_height">1.5dp</dimen>
<dimen name="config_clipboard_pinned_icon_size">18dp</dimen>
<!-- Inset used in Accessibility mode to avoid accidental key presses when a finger slides off the screen. -->
<dimen name="config_accessibility_edge_slop">8dp</dimen>

View file

@ -93,6 +93,9 @@
<fraction name="config_emoji_keyboard_key_label_size">78%p</fraction>
<integer name="config_emoji_keyboard_max_recents_key_count">32</integer>
<!-- Clipboard keyboard -->
<integer name="config_clipboard_keyboard_col_count">2</integer>
<!-- Key codes of hardware keys that can be used to toggle the Emoji layout.
Each array defines a comma-separated tuple containing:
1. Key code constant from android.view.KeyEvent

View file

@ -29,7 +29,6 @@
<item name="iconSearchKey">@drawable/sym_keyboard_search_holo_dark</item>
<item name="iconTabKey">@drawable/sym_keyboard_tab_holo_dark</item>
<item name="iconShortcutKey">@drawable/sym_keyboard_voice_holo_dark</item>
<item name="iconClipboardKey">@drawable/sym_keyboard_clipboard_dark</item>
<item name="iconIncognitoKey">@drawable/sym_keyboard_incognito_dark</item>
<item name="iconSpaceKeyForNumberLayout">@drawable/sym_keyboard_space_holo_dark</item>
<item name="iconShiftKeyShifted">@drawable/sym_keyboard_shift_locked_holo_dark</item>
@ -39,5 +38,8 @@
<item name="iconZwjKey">@drawable/sym_keyboard_zwj_holo_dark</item>
<item name="iconEmojiActionKey">@drawable/sym_keyboard_smiley_holo_dark</item>
<item name="iconEmojiNormalKey">@drawable/sym_keyboard_smiley_holo_dark</item>
<item name="iconClipboardActionKey">@drawable/sym_keyboard_clipboard_holo_dark</item>
<item name="iconClipboardNormalKey">@drawable/sym_keyboard_clipboard_holo_dark</item>
<item name="iconClearClipboardKey">@drawable/sym_keyboard_clear_clipboard_holo_dark</item>
</style>
</resources>

View file

@ -36,7 +36,6 @@
<item name="iconPreviousKey">@drawable/sym_keyboard_previous_lxx_dark</item>
<item name="iconShortcutKey">@drawable/sym_keyboard_voice_lxx_dark</item>
<item name="iconShortcutKeyDisabled">@drawable/sym_keyboard_voice_off_lxx_dark</item>
<item name="iconClipboardKey">@drawable/sym_keyboard_clipboard_dark</item>
<item name="iconIncognitoKey">@drawable/sym_keyboard_incognito_lxx_dark</item>
<item name="iconSpaceKeyForNumberLayout">@drawable/sym_keyboard_space_lxx_dark</item>
<item name="iconLanguageSwitchKey">@drawable/sym_keyboard_language_switch_lxx_dark</item>
@ -44,5 +43,8 @@
<item name="iconZwjKey">@drawable/sym_keyboard_zwj_lxx_dark</item>
<item name="iconEmojiActionKey">@drawable/sym_keyboard_smiley_lxx_dark</item>
<item name="iconEmojiNormalKey">@drawable/sym_keyboard_smiley_lxx_dark</item>
<item name="iconClipboardActionKey">@drawable/sym_keyboard_clipboard_lxx_dark</item>
<item name="iconClipboardNormalKey">@drawable/sym_keyboard_clipboard_lxx_dark</item>
<item name="iconClearClipboardKey">@drawable/sym_keyboard_clear_clipboard_lxx_dark</item>
</style>
</resources>

View file

@ -36,7 +36,6 @@
<item name="iconPreviousKey">@drawable/sym_keyboard_previous_lxx_light</item>
<item name="iconShortcutKey">@drawable/sym_keyboard_voice_lxx_light</item>
<item name="iconShortcutKeyDisabled">@drawable/sym_keyboard_voice_off_lxx_light</item>
<item name="iconClipboardKey">@drawable/sym_keyboard_clipboard_light</item>
<item name="iconIncognitoKey">@drawable/sym_keyboard_incognito_lxx_light</item>
<item name="iconSpaceKeyForNumberLayout">@drawable/sym_keyboard_space_lxx_light</item>
<item name="iconLanguageSwitchKey">@drawable/sym_keyboard_language_switch_lxx_light</item>
@ -44,5 +43,8 @@
<item name="iconZwjKey">@drawable/sym_keyboard_zwj_lxx_light</item>
<item name="iconEmojiActionKey">@drawable/sym_keyboard_smiley_lxx_dark</item>
<item name="iconEmojiNormalKey">@drawable/sym_keyboard_smiley_lxx_light</item>
<item name="iconClipboardActionKey">@drawable/sym_keyboard_clipboard_lxx_dark</item>
<item name="iconClipboardNormalKey">@drawable/sym_keyboard_clipboard_lxx_light</item>
<item name="iconClearClipboardKey">@drawable/sym_keyboard_clear_clipboard_lxx_light</item>
</style>
</resources>

View file

@ -110,6 +110,7 @@
delete button, need themed {@link org.dslul.openboard.inputmethod.keyboard.KeyboardView}
attributes. -->
<style name="EmojiPalettesView" />
<style name="ClipboardHistoryView" />
<style name="MoreKeysKeyboard" />
<style name="MoreKeysKeyboardView" />
<style name="SuggestionStripView" />

View file

@ -25,6 +25,7 @@
<item name="keyboardViewStyle">@style/KeyboardView.ICS</item>
<item name="mainKeyboardViewStyle">@style/MainKeyboardView.ICS</item>
<item name="emojiPalettesViewStyle">@style/EmojiPalettesView.ICS</item>
<item name="clipboardHistoryViewStyle">@style/ClipboardHistoryView.ICS</item>
<item name="moreKeysKeyboardStyle">@style/MoreKeysKeyboard.ICS</item>
<!-- Note: ICS theme uses the same style for both general more keys and action more keys. -->
<item name="moreKeysKeyboardViewStyle">@style/MoreKeysKeyboardView.ICS</item>
@ -98,6 +99,13 @@
<item name="iconEmojiCategory9Tab">@drawable/ic_emoji_flags_holo_dark</item>
<item name="iconEmojiCategory10Tab">@drawable/ic_emoji_emoticons_holo_dark</item>
</style>
<style
name="ClipboardHistoryView.ICS"
parent="MainKeyboardView.ICS"
>
<item name="iconPinnedClip">@drawable/ic_clipboard_pin_holo_dark</item>
<item name="dividerBackground">@color/emoji_tab_page_indicator_background_holo</item>
</style>
<style
name="MoreKeysKeyboard.ICS"
parent="Keyboard.ICS"

View file

@ -25,6 +25,7 @@
<item name="keyboardViewStyle">@style/KeyboardView.KLP</item>
<item name="mainKeyboardViewStyle">@style/MainKeyboardView.KLP</item>
<item name="emojiPalettesViewStyle">@style/EmojiPalettesView.KLP</item>
<item name="clipboardHistoryViewStyle">@style/ClipboardHistoryView.KLP</item>
<item name="moreKeysKeyboardStyle">@style/MoreKeysKeyboard.KLP</item>
<!-- Note: KLP theme uses the same style for both general more keys and action more keys. -->
<item name="moreKeysKeyboardViewStyle">@style/MoreKeysKeyboardView.KLP</item>
@ -98,6 +99,11 @@
<item name="iconEmojiCategory9Tab">@drawable/ic_emoji_flags_holo_dark</item>
<item name="iconEmojiCategory10Tab">@drawable/ic_emoji_emoticons_holo_dark</item>
</style>
<style
name="ClipboardHistoryView.KLP"
parent="ClipboardHistoryView.ICS"
>
</style>
<style
name="MoreKeysKeyboard.KLP"
parent="Keyboard.KLP"

View file

@ -28,6 +28,7 @@
<item name="keyboardViewStyle">@style/KeyboardView.LXX_Dark_Amoled</item>
<item name="mainKeyboardViewStyle">@style/MainKeyboardView.LXX_Dark_Amoled</item>
<item name="emojiPalettesViewStyle">@style/EmojiPalettesView.LXX_Dark_Amoled</item>
<item name="clipboardHistoryViewStyle">@style/ClipboardHistoryView.LXX_Dark_Amoled</item>
<item name="moreKeysKeyboardViewStyle">@style/MoreKeysKeyboardView.LXX_Dark_Amoled</item>
<item name="suggestionStripViewStyle">@style/SuggestionStripView.LXX_Dark_Amoled</item>
</style>
@ -65,6 +66,14 @@
<item name="keyBackground">@drawable/btn_keyboard_key_lxx_dark_amoled</item>
</style>
<style
name="ClipboardHistoryView.LXX_Dark_Amoled"
parent="ClipboardHistoryView.LXX_Dark"
>
<item name="android:background">@color/background_amoled_black</item>
<item name="keyBackground">@drawable/btn_keyboard_key_lxx_dark_amoled</item>
</style>
<style
name="MoreKeysKeyboardView.LXX_Dark_Amoled"
parent="MoreKeysKeyboardView.LXX_Dark"

View file

@ -26,6 +26,7 @@
<item name="keyboardViewStyle">@style/KeyboardView.LXX_Dark_Border</item>
<item name="mainKeyboardViewStyle">@style/MainKeyboardView.LXX_Dark_Border</item>
<item name="emojiPalettesViewStyle">@style/EmojiPalettesView.LXX_Dark_Border</item>
<item name="clipboardHistoryViewStyle">@style/ClipboardHistoryView.LXX_Dark_Border</item>
<item name="moreKeysKeyboardViewStyle">@style/MoreKeysKeyboardView.LXX_Dark_Border</item>
<item name="suggestionStripViewStyle">@style/SuggestionStripView.LXX_Dark_Border</item>
</style>
@ -65,6 +66,14 @@
<item name="spacebarBackground">@drawable/btn_keyboard_key_lxx_dark_border</item>
</style>
<style
name="ClipboardHistoryView.LXX_Dark_Border"
parent="ClipboardHistoryView.LXX_Dark">
<item name="android:background">@drawable/keyboard_background_lxx_dark_border</item>
<item name="keyBackground">@drawable/btn_keyboard_key_lxx_dark_border</item>
<item name="functionalKeyBackground">@drawable/btn_keyboard_key_functional_lxx_dark_border</item>
</style>
<style
name="MoreKeysKeyboardView.LXX_Dark_Border"
parent="MoreKeysKeyboardView.LXX_Dark">

View file

@ -28,6 +28,7 @@
<item name="keyboardViewStyle">@style/KeyboardView.LXX_Dark</item>
<item name="mainKeyboardViewStyle">@style/MainKeyboardView.LXX_Dark</item>
<item name="emojiPalettesViewStyle">@style/EmojiPalettesView.LXX_Dark</item>
<item name="clipboardHistoryViewStyle">@style/ClipboardHistoryView.LXX_Dark</item>
<item name="moreKeysKeyboardStyle">@style/MoreKeysKeyboard.LXX_Dark</item>
<item name="moreKeysKeyboardViewStyle">@style/MoreKeysKeyboardView.LXX_Dark</item>
<item name="moreKeysKeyboardViewForActionStyle">@style/MoreKeysKeyboardView.LXX_Dark.Action</item>
@ -99,6 +100,13 @@
<item name="iconEmojiCategory9Tab">@drawable/ic_emoji_flags_lxx_dark</item>
<item name="iconEmojiCategory10Tab">@drawable/ic_emoji_emoticons_lxx_dark</item>
</style>
<style
name="ClipboardHistoryView.LXX_Dark"
parent="MainKeyboardView.LXX_Dark"
>
<item name="iconPinnedClip">@drawable/ic_clipboard_pin_lxx_dark</item>
<item name="dividerBackground">@color/emoji_tab_page_indicator_background_lxx_dark</item>
</style>
<style
name="MoreKeysKeyboard.LXX_Dark"
parent="Keyboard.LXX_Dark"

View file

@ -25,6 +25,7 @@
<item name="keyboardViewStyle">@style/KeyboardView.LXX_Light_Border</item>
<item name="mainKeyboardViewStyle">@style/MainKeyboardView.LXX_Light_Border</item>
<item name="emojiPalettesViewStyle">@style/EmojiPalettesView.LXX_Light_Border</item>
<item name="clipboardHistoryViewStyle">@style/ClipboardHistoryView.LXX_Light_Border</item>
<item name="moreKeysKeyboardViewStyle">@style/MoreKeysKeyboardView.LXX_Light_Border</item>
<item name="suggestionStripViewStyle">@style/SuggestionStripView.LXX_Light_Border</item>
</style>
@ -62,6 +63,14 @@
<item name="spacebarBackground">@drawable/btn_keyboard_key_lxx_light_border</item>
</style>
<style
name="ClipboardHistoryView.LXX_Light_Border"
parent="ClipboardHistoryView.LXX_Light">
<item name="android:background">@drawable/keyboard_background_lxx_light_border</item>
<item name="keyBackground">@drawable/btn_keyboard_key_lxx_light_border</item>
<item name="functionalKeyBackground">@drawable/btn_keyboard_key_functional_lxx_light_border</item>
</style>
<style
name="MoreKeysKeyboardView.LXX_Light_Border"
parent="MoreKeysKeyboardView.LXX_Light">

View file

@ -25,6 +25,7 @@
<item name="keyboardViewStyle">@style/KeyboardView.LXX_Light</item>
<item name="mainKeyboardViewStyle">@style/MainKeyboardView.LXX_Light</item>
<item name="emojiPalettesViewStyle">@style/EmojiPalettesView.LXX_Light</item>
<item name="clipboardHistoryViewStyle">@style/ClipboardHistoryView.LXX_Light</item>
<item name="moreKeysKeyboardStyle">@style/MoreKeysKeyboard.LXX_Light</item>
<item name="moreKeysKeyboardViewStyle">@style/MoreKeysKeyboardView.LXX_Light</item>
<item name="moreKeysKeyboardViewForActionStyle">@style/MoreKeysKeyboardView.LXX_Light.Action</item>
@ -96,6 +97,13 @@
<item name="iconEmojiCategory9Tab">@drawable/ic_emoji_flags_lxx_light</item>
<item name="iconEmojiCategory10Tab">@drawable/ic_emoji_emoticons_lxx_light</item>
</style>
<style
name="ClipboardHistoryView.LXX_Light"
parent="MainKeyboardView.LXX_Light"
>
<item name="iconPinnedClip">@drawable/ic_clipboard_pin_lxx_light</item>
<item name="dividerBackground">@color/emoji_tab_page_indicator_background_lxx_light</item>
</style>
<style
name="MoreKeysKeyboard.LXX_Light"
parent="Keyboard.LXX_Light"

View file

@ -26,61 +26,61 @@
<key-style
latin:styleName="navigateNextMoreKeysStyle"
latin:keyLabelFlags="hasPopupHint|preserveCase"
latin:moreKeys="!text/keyspec_action_next" />
latin:moreKeys="!text/keyspec_clipboard_action_key,!text/keyspec_action_next" />
<key-style
latin:styleName="navigatePreviousMoreKeysStyle"
latin:keyLabelFlags="hasPopupHint|preserveCase"
latin:moreKeys="!text/keyspec_action_previous" />
latin:moreKeys="!text/keyspec_action_previous,!text/keyspec_clipboard_action_key" />
<key-style
latin:styleName="navigatePreviousNextMoreKeysStyle"
latin:keyLabelFlags="hasPopupHint|preserveCase"
latin:moreKeys="!fixedColumnOrder!2,!needsDividers!,!text/keyspec_action_previous,!text/keyspec_action_next" />
latin:moreKeys="!fixedColumnOrder!3,!needsDividers!,!text/keyspec_action_previous,!text/keyspec_clipboard_action_key,!text/keyspec_action_next" />
<key-style
latin:styleName="navigateEmojiMoreKeysStyle"
latin:keyLabelFlags="hasPopupHint|preserveCase"
latin:moreKeys="!text/keyspec_emoji_action_key" />
latin:moreKeys="!text/keyspec_clipboard_action_key,!text/keyspec_emoji_action_key" />
<key-style
latin:styleName="navigateEmojiNextMoreKeysStyle"
latin:keyLabelFlags="hasPopupHint|preserveCase"
latin:moreKeys="!fixedColumnOrder!2,!needsDividers!,!text/keyspec_emoji_action_key,!text/keyspec_action_next" />
latin:moreKeys="!fixedColumnOrder!3,!needsDividers!,!text/keyspec_clipboard_action_key,!text/keyspec_emoji_action_key,!text/keyspec_action_next" />
<key-style
latin:styleName="navigateEmojiPreviousMoreKeysStyle"
latin:keyLabelFlags="hasPopupHint|preserveCase"
latin:moreKeys="!fixedColumnOrder!2,!needsDividers!,!text/keyspec_emoji_action_key,!text/keyspec_action_previous" />
latin:moreKeys="!fixedColumnOrder!3,!needsDividers!,!text/keyspec_action_previous,!text/keyspec_clipboard_action_key,!text/keyspec_emoji_action_key" />
<key-style
latin:styleName="navigateEmojiPreviousNextMoreKeysStyle"
latin:keyLabelFlags="hasPopupHint|preserveCase"
latin:moreKeys="!fixedColumnOrder!3,!needsDividers!,!text/keyspec_emoji_action_key,!text/keyspec_action_previous,!text/keyspec_action_next" />
latin:moreKeys="!fixedColumnOrder!4,!needsDividers!,!text/keyspec_action_previous,!text/keyspec_clipboard_action_key,!text/keyspec_emoji_action_key,!text/keyspec_action_next" />
</case>
<default>
<key-style
latin:styleName="navigateNextMoreKeysStyle"
latin:keyLabelFlags="hasPopupHint|preserveCase"
latin:moreKeys="!icon/next_key|!code/key_action_next" />
latin:moreKeys="!text/keyspec_clipboard_action_key,!icon/next_key|!code/key_action_next" />
<key-style
latin:styleName="navigatePreviousMoreKeysStyle"
latin:keyLabelFlags="hasPopupHint|preserveCase"
latin:moreKeys="!icon/previous_key|!code/key_action_previous" />
latin:moreKeys="!icon/previous_key|!code/key_action_previous,!text/keyspec_clipboard_action_key" />
<key-style
latin:styleName="navigatePreviousNextMoreKeysStyle"
latin:keyLabelFlags="hasPopupHint|preserveCase"
latin:moreKeys="!fixedColumnOrder!2,!needsDividers!,!icon/previous_key|!code/key_action_previous,!icon/next_key|!code/key_action_next" />
latin:moreKeys="!fixedColumnOrder!3,!needsDividers!,!icon/previous_key|!code/key_action_previous,!text/keyspec_clipboard_action_key,!icon/next_key|!code/key_action_next" />
<key-style
latin:styleName="navigateEmojiMoreKeysStyle"
latin:keyLabelFlags="hasPopupHint|preserveCase"
latin:moreKeys="!text/keyspec_emoji_action_key" />
latin:moreKeys="!text/keyspec_clipboard_action_key,!text/keyspec_emoji_action_key" />
<key-style
latin:styleName="navigateEmojiNextMoreKeysStyle"
latin:keyLabelFlags="hasPopupHint|preserveCase"
latin:moreKeys="!fixedColumnOrder!2,!needsDividers!,!text/keyspec_emoji_action_key,!icon/next_key|!code/key_action_next" />
latin:moreKeys="!fixedColumnOrder!3,!needsDividers!,!text/keyspec_clipboard_action_key,!text/keyspec_emoji_action_key,!icon/next_key|!code/key_action_next" />
<key-style
latin:styleName="navigateEmojiPreviousMoreKeysStyle"
latin:keyLabelFlags="hasPopupHint|preserveCase"
latin:moreKeys="!fixedColumnOrder!2,!needsDividers!,!text/keyspec_emoji_action_key,!icon/previous_key|!code/key_action_previous" />
latin:moreKeys="!fixedColumnOrder!3,!needsDividers!,!icon/previous_key|!code/key_action_previous,!text/keyspec_clipboard_action_key,!text/keyspec_emoji_action_key" />
<key-style
latin:styleName="navigateEmojiPreviousNextMoreKeysStyle"
latin:keyLabelFlags="hasPopupHint|preserveCase"
latin:moreKeys="!fixedColumnOrder!3,!needsDividers!,!text/keyspec_emoji_action_key,!icon/previous_key|!code/key_action_previous,!icon/next_key|!code/key_action_next" />
latin:moreKeys="!fixedColumnOrder!4,!needsDividers!,!icon/previous_key|!code/key_action_previous,!text/keyspec_clipboard_action_key,!text/keyspec_emoji_action_key,!icon/next_key|!code/key_action_next" />
</default>
</switch>
</merge>

View file

@ -30,12 +30,41 @@
latin:styleName="settingsMoreKeysStyle"
latin:backgroundType="functional" />
</case>
<!-- clobberSettingsKey="false" -->
<case
latin:emojiKeyEnabled="true"
latin:languageSwitchKeyEnabled="true"
>
<key-style
latin:styleName="settingsMoreKeysStyle"
latin:keyLabelFlags="hasPopupHint"
latin:additionalMoreKeys="!text/keyspec_settings,!text/keyspec_clipboard_normal_key"
latin:backgroundType="functional" />
</case>
<case
latin:emojiKeyEnabled="false"
latin:languageSwitchKeyEnabled="true"
>
<key-style
latin:styleName="settingsMoreKeysStyle"
latin:keyLabelFlags="hasPopupHint"
latin:additionalMoreKeys="!text/keyspec_settings,!text/keyspec_clipboard_normal_key,!text/keyspec_emoji_normal_key"
latin:backgroundType="functional" />
</case>
<case
latin:emojiKeyEnabled="true"
latin:languageSwitchKeyEnabled="false"
>
<key-style
latin:styleName="settingsMoreKeysStyle"
latin:keyLabelFlags="hasPopupHint"
latin:additionalMoreKeys="!text/keyspec_settings,!icon/language_switch_key|!code/key_language_switch,!text/keyspec_clipboard_normal_key"
latin:backgroundType="functional" />
</case>
<default>
<key-style
latin:styleName="settingsMoreKeysStyle"
latin:keyLabelFlags="hasPopupHint"
latin:additionalMoreKeys="!text/keyspec_settings,!icon/language_switch_key|!code/key_language_switch,!text/keyspec_emoji_normal_key"
latin:additionalMoreKeys="!text/keyspec_settings,!icon/language_switch_key|!code/key_language_switch,!text/keyspec_clipboard_normal_key,!text/keyspec_emoji_normal_key"
latin:backgroundType="functional" />
</default>
</switch>