diff --git a/app/src/main/java/helium314/keyboard/latin/suggestions/SuggestionStripView.java b/app/src/main/java/helium314/keyboard/latin/suggestions/SuggestionStripView.java deleted file mode 100644 index bab6e4157..000000000 --- a/app/src/main/java/helium314/keyboard/latin/suggestions/SuggestionStripView.java +++ /dev/null @@ -1,752 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * modified - * SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only - */ - -package helium314.keyboard.latin.suggestions; - -import static helium314.keyboard.latin.utils.ToolbarUtilsKt.*; - -import android.annotation.SuppressLint; -import android.app.KeyguardManager; -import android.content.Context; -import android.content.SharedPreferences; -import android.content.res.Resources; -import android.graphics.Color; -import android.graphics.Typeface; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.GradientDrawable; -import android.os.Build; -import android.text.TextUtils; -import android.util.AttributeSet; -import android.util.TypedValue; -import android.view.GestureDetector; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.View.OnLongClickListener; -import android.view.ViewGroup; -import android.view.ViewParent; -import android.view.accessibility.AccessibilityEvent; -import android.widget.ImageButton; -import android.widget.LinearLayout; -import android.widget.RelativeLayout; -import android.widget.TextView; - -import helium314.keyboard.accessibility.AccessibilityUtils; -import helium314.keyboard.keyboard.Keyboard; -import helium314.keyboard.keyboard.KeyboardSwitcher; -import helium314.keyboard.keyboard.MainKeyboardView; -import helium314.keyboard.keyboard.PopupKeysPanel; -import helium314.keyboard.keyboard.internal.KeyboardIconsSet; -import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode; -import helium314.keyboard.latin.AudioAndHapticFeedbackManager; -import helium314.keyboard.latin.Dictionary; -import helium314.keyboard.latin.R; -import helium314.keyboard.latin.SuggestedWords; -import helium314.keyboard.latin.SuggestedWords.SuggestedWordInfo; -import helium314.keyboard.latin.common.ColorType; -import helium314.keyboard.latin.common.Colors; -import helium314.keyboard.latin.common.Constants; -import helium314.keyboard.latin.define.DebugFlags; -import helium314.keyboard.latin.settings.DebugSettings; -import helium314.keyboard.latin.settings.Defaults; -import helium314.keyboard.latin.settings.Settings; -import helium314.keyboard.latin.settings.SettingsValues; -import helium314.keyboard.latin.suggestions.PopupSuggestionsView.MoreSuggestionsListener; -import helium314.keyboard.latin.utils.KtxKt; -import helium314.keyboard.latin.utils.Log; -import helium314.keyboard.latin.utils.ToolbarKey; -import helium314.keyboard.latin.utils.ToolbarMode; -import helium314.keyboard.latin.utils.ToolbarUtilsKt; - -import java.util.ArrayList; -import java.util.concurrent.atomic.AtomicBoolean; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -public final class SuggestionStripView extends RelativeLayout implements OnClickListener, - OnLongClickListener, SharedPreferences.OnSharedPreferenceChangeListener { - public interface Listener { - void pickSuggestionManually(SuggestedWordInfo word); - void onCodeInput(int primaryCode, int x, int y, boolean isKeyRepeat); - void removeSuggestion(final String word); - } - - public static boolean DEBUG_SUGGESTIONS; - private static final float DEBUG_INFO_TEXT_SIZE_IN_DIP = 6.5f; - private static final String TAG = SuggestionStripView.class.getSimpleName(); - - private final ViewGroup mSuggestionsStrip; - private final ImageButton mToolbarExpandKey; - private final Drawable mIncognitoIcon; - private final Drawable mToolbarArrowIcon; - private final Drawable mBinIcon; - private final ViewGroup mToolbar; - private final View mToolbarContainer; - private final ViewGroup mPinnedKeys; - private final GradientDrawable mEnabledToolKeyBackground = new GradientDrawable(); - private final Drawable mDefaultBackground; - MainKeyboardView mMainKeyboardView; - - private final View mMoreSuggestionsContainer; - private final PopupSuggestionsView mMoreSuggestionsView; - private final MoreSuggestions.Builder mMoreSuggestionsBuilder; - - private final ArrayList mWordViews = new ArrayList<>(); - private final ArrayList mDebugInfoViews = new ArrayList<>(); - private final ArrayList mDividerViews = new ArrayList<>(); - - Listener mListener; - private SuggestedWords mSuggestedWords = SuggestedWords.getEmptyInstance(); - private int mStartIndexOfMoreSuggestions; - private int mRtl = 1; // 1 if LTR, -1 if RTL - - private final SuggestionStripLayoutHelper mLayoutHelper; - private final StripVisibilityGroup mStripVisibilityGroup; - private boolean isExternalSuggestionVisible = false; // Required to disable the more suggestions if other suggestions are visible - private final ToolbarMode mToolbarMode; - - private static class StripVisibilityGroup { - private final View mSuggestionStripView; - private final View mSuggestionsStrip; - - public StripVisibilityGroup(final View suggestionStripView, - final ViewGroup suggestionsStrip) { - mSuggestionStripView = suggestionStripView; - mSuggestionsStrip = suggestionsStrip; - showSuggestionsStrip(); - } - - public void setLayoutDirection(final int layoutDirection) { - mSuggestionStripView.setLayoutDirection(layoutDirection); - mSuggestionsStrip.setLayoutDirection(layoutDirection); - } - - public void showSuggestionsStrip() { - mSuggestionsStrip.setVisibility(VISIBLE); - } - - } - - /** - * Construct a {@link SuggestionStripView} for showing suggestions to be picked by the user. - */ - public SuggestionStripView(final Context context, final AttributeSet attrs) { - this(context, attrs, R.attr.suggestionStripViewStyle); - } - - @SuppressLint("InflateParams") // does not seem suitable here - public SuggestionStripView(final Context context, final AttributeSet attrs, final int defStyle) { - super(context, attrs, defStyle); - final Colors colors = Settings.getValues().mColors; - final SharedPreferences prefs = KtxKt.prefs(context); - DEBUG_SUGGESTIONS = prefs.getBoolean(DebugSettings.PREF_SHOW_SUGGESTION_INFOS, Defaults.PREF_SHOW_SUGGESTION_INFOS); - - final LayoutInflater inflater = LayoutInflater.from(context); - inflater.inflate(R.layout.suggestions_strip, this); - - mSuggestionsStrip = findViewById(R.id.suggestions_strip); - mToolbarExpandKey = findViewById(R.id.suggestions_strip_toolbar_key); - mStripVisibilityGroup = new StripVisibilityGroup(this, mSuggestionsStrip); - mPinnedKeys = findViewById(R.id.pinned_keys); - mToolbar = findViewById(R.id.toolbar); - mToolbarContainer = findViewById(R.id.toolbar_container); - mToolbarMode = Settings.getValues().mToolbarMode; - - if (mToolbarMode == ToolbarMode.TOOLBAR_KEYS) { - setToolbarVisibility(true); - } - - final Typeface customTypeface = Settings.getInstance().getCustomTypeface(); - for (int pos = 0; pos < SuggestedWords.MAX_SUGGESTIONS; pos++) { - final TextView word = new TextView(context, null, R.attr.suggestionWordStyle); - word.setContentDescription(getResources().getString(R.string.spoken_empty_suggestion)); - word.setOnClickListener(this); - word.setOnLongClickListener(this); - if (customTypeface != null) - word.setTypeface(customTypeface); - colors.setBackground(word, ColorType.STRIP_BACKGROUND); - mWordViews.add(word); - final View divider = inflater.inflate(R.layout.suggestion_divider, null); - mDividerViews.add(divider); - final TextView info = new TextView(context, null, R.attr.suggestionWordStyle); - info.setTextColor(colors.get(ColorType.KEY_TEXT)); - info.setTextSize(TypedValue.COMPLEX_UNIT_DIP, DEBUG_INFO_TEXT_SIZE_IN_DIP); - mDebugInfoViews.add(info); - } - - mLayoutHelper = new SuggestionStripLayoutHelper(context, attrs, defStyle, mWordViews, mDividerViews, mDebugInfoViews); - - mMoreSuggestionsContainer = inflater.inflate(R.layout.more_suggestions, null); - mMoreSuggestionsView = mMoreSuggestionsContainer.findViewById(R.id.more_suggestions_view); - mMoreSuggestionsBuilder = new MoreSuggestions.Builder(context, mMoreSuggestionsView); - - final Resources res = context.getResources(); - mMoreSuggestionsModalTolerance = res.getDimensionPixelOffset( - R.dimen.config_more_suggestions_modal_tolerance); - mMoreSuggestionsSlidingDetector = new GestureDetector(context, mMoreSuggestionsSlidingListener); - - final KeyboardIconsSet iconsSet = KeyboardIconsSet.Companion.getInstance(); - mIncognitoIcon = iconsSet.getNewDrawable(ToolbarKey.INCOGNITO.name(), context); - mToolbarArrowIcon = iconsSet.getNewDrawable(KeyboardIconsSet.NAME_TOOLBAR_KEY, context); - mBinIcon = iconsSet.getNewDrawable(KeyboardIconsSet.NAME_BIN, context); - - final LinearLayout.LayoutParams toolbarKeyLayoutParams = new LinearLayout.LayoutParams( - getResources().getDimensionPixelSize(R.dimen.config_suggestions_strip_edge_key_width), - LinearLayout.LayoutParams.MATCH_PARENT - ); - if (mToolbarMode == ToolbarMode.TOOLBAR_KEYS || mToolbarMode == ToolbarMode.EXPANDABLE) { - for (final ToolbarKey key : ToolbarUtilsKt.getEnabledToolbarKeys(prefs)) { - final ImageButton button = createToolbarKey(context, iconsSet, key); - button.setLayoutParams(toolbarKeyLayoutParams); - setupKey(button, colors); - mToolbar.addView(button); - } - } - - final int toolbarHeight = Math.min(mToolbarExpandKey.getLayoutParams().height, (int) getResources().getDimension(R.dimen.config_suggestions_strip_height)); - mToolbarExpandKey.getLayoutParams().height = toolbarHeight; - mToolbarExpandKey.getLayoutParams().width = toolbarHeight; // we want it square - colors.setBackground(mToolbarExpandKey, ColorType.STRIP_BACKGROUND); - mDefaultBackground = mToolbarExpandKey.getBackground(); - mEnabledToolKeyBackground.setColors(new int[] {colors.get(ColorType.TOOL_BAR_KEY_ENABLED_BACKGROUND) | 0xFF000000, Color.TRANSPARENT}); // ignore alpha on accent color - mEnabledToolKeyBackground.setGradientType(GradientDrawable.RADIAL_GRADIENT); - mEnabledToolKeyBackground.setGradientRadius(mToolbarExpandKey.getLayoutParams().height / 2f); // nothing else has a usable height at this state - - colors.setColor(mToolbarExpandKey, ColorType.TOOL_BAR_EXPAND_KEY); - colors.setColor(mToolbarExpandKey.getBackground(), ColorType.TOOL_BAR_EXPAND_KEY_BACKGROUND); - - if (!Settings.getValues().mSuggestionStripHiddenPerUserSettings) { - for (final ToolbarKey pinnedKey : ToolbarUtilsKt.getPinnedToolbarKeys(prefs)) { - final ImageButton button = createToolbarKey(context, iconsSet, pinnedKey); - button.setLayoutParams(toolbarKeyLayoutParams); - setupKey(button, colors); - mPinnedKeys.addView(button); - final View pinnedKeyInToolbar = mToolbar.findViewWithTag(pinnedKey); - if (pinnedKeyInToolbar != null && Settings.getValues().mQuickPinToolbarKeys) - pinnedKeyInToolbar.setBackground(mEnabledToolKeyBackground); - } - } - - colors.setBackground(this, ColorType.STRIP_BACKGROUND); - updateKeys(); - } - - @Override - public void onSharedPreferenceChanged(SharedPreferences prefs, @Nullable String key) { - ToolbarUtilsKt.setToolbarButtonsActivatedStateOnPrefChange(mPinnedKeys, key); - ToolbarUtilsKt.setToolbarButtonsActivatedStateOnPrefChange(mToolbar, key); - } - - /** - * A connection back to the input method. - */ - public void setListener(final Listener listener, final View inputView) { - mListener = listener; - mMainKeyboardView = inputView.findViewById(R.id.keyboard_view); - } - - private void updateKeys() { - final SettingsValues currentSettingsValues = Settings.getValues(); - final View toolbarVoiceKey = mToolbar.findViewWithTag(ToolbarKey.VOICE); - if (toolbarVoiceKey != null) - toolbarVoiceKey.setVisibility(currentSettingsValues.mShowsVoiceInputKey ? VISIBLE : GONE); - final View pinnedVoiceKey = mPinnedKeys.findViewWithTag(ToolbarKey.VOICE); - if (pinnedVoiceKey != null) - pinnedVoiceKey.setVisibility(currentSettingsValues.mShowsVoiceInputKey ? VISIBLE : GONE); - final boolean toolbarIsExpandable = mToolbarMode == ToolbarMode.EXPANDABLE; - if (currentSettingsValues.mIncognitoModeEnabled) { - mToolbarExpandKey.setImageDrawable(mIncognitoIcon); - mToolbarExpandKey.setVisibility(VISIBLE); - } else { - mToolbarExpandKey.setImageDrawable(mToolbarArrowIcon); - mToolbarExpandKey.setVisibility(toolbarIsExpandable ? VISIBLE : GONE); - } - mToolbarExpandKey.setScaleX((mToolbarContainer.getVisibility() != VISIBLE ? 1f : -1f) * mRtl); - - // hide pinned keys if device is locked, and avoid expanding toolbar - final KeyguardManager km = (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE); - final boolean hideToolbarKeys = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1 - ? km.isDeviceLocked() - : km.isKeyguardLocked(); - mToolbarExpandKey.setOnClickListener(hideToolbarKeys || !toolbarIsExpandable ? null : this); - mPinnedKeys.setVisibility(hideToolbarKeys ? GONE : mSuggestionsStrip.getVisibility()); - isExternalSuggestionVisible = false; - } - - public void setRtl(final boolean isRtlLanguage) { - final int layoutDirection; - if (!Settings.getValues().mVarToolbarDirection) - layoutDirection = View.LAYOUT_DIRECTION_LOCALE; - else{ - layoutDirection = isRtlLanguage ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR; - mRtl = isRtlLanguage ? -1 : 1; - } - mStripVisibilityGroup.setLayoutDirection(layoutDirection); - } - - public void setSuggestions(final SuggestedWords suggestedWords, final boolean isRtlLanguage) { - clear(); - setRtl(isRtlLanguage); - mSuggestedWords = suggestedWords; - mStartIndexOfMoreSuggestions = mLayoutHelper.layoutAndReturnStartIndexOfMoreSuggestions( - getContext(), mSuggestedWords, mSuggestionsStrip, this); - } - - public void setExternalSuggestionView(final View view) { - clear(); - isExternalSuggestionVisible = true; - mSuggestionsStrip.addView(view); - if (Settings.getValues().mAutoHideToolbar) - setToolbarVisibility(false); - } - - @Override - public void onVisibilityChanged(@NonNull final View view, final int visibility) { - super.onVisibilityChanged(view, visibility); - if (view == this) - // workaround for a bug with inline suggestions views that just keep showing up otherwise, https://github.com/Helium314/HeliBoard/pull/386 - mSuggestionsStrip.setVisibility(visibility); - } - - public void setMoreSuggestionsHeight(final int remainingHeight) { - mLayoutHelper.setMoreSuggestionsHeight(remainingHeight); - } - - @SuppressLint("ClickableViewAccessibility") // why would "null" need to call View#performClick? - private void clear() { - mSuggestionsStrip.removeAllViews(); - if (DEBUG_SUGGESTIONS) - removeAllDebugInfoViews(); - if (mToolbarContainer.getVisibility() != VISIBLE) - mStripVisibilityGroup.showSuggestionsStrip(); - dismissMoreSuggestionsPanel(); - for (final TextView word : mWordViews) { - word.setOnTouchListener(null); - } - } - - private void removeAllDebugInfoViews() { - // The debug info views may be placed as children views of this {@link SuggestionStripView}. - for (final View debugInfoView : mDebugInfoViews) { - final ViewParent parent = debugInfoView.getParent(); - if (parent instanceof ViewGroup) { - ((ViewGroup)parent).removeView(debugInfoView); - } - } - } - - private final MoreSuggestionsListener mMoreSuggestionsListener = new MoreSuggestionsListener() { - @Override - public void onSuggestionSelected(final SuggestedWordInfo wordInfo) { - mListener.pickSuggestionManually(wordInfo); - dismissMoreSuggestionsPanel(); - } - - @Override - public void onCancelInput() { - dismissMoreSuggestionsPanel(); - } - }; - - private final PopupKeysPanel.Controller mMoreSuggestionsController = - new PopupKeysPanel.Controller() { - @Override - public void onDismissPopupKeysPanel() { - mMainKeyboardView.onDismissPopupKeysPanel(); - } - - @Override - public void onShowPopupKeysPanel(final PopupKeysPanel panel) { - mMainKeyboardView.onShowPopupKeysPanel(panel); - } - - @Override - public void onCancelPopupKeysPanel() { - dismissMoreSuggestionsPanel(); - } - }; - - public boolean isShowingMoreSuggestionPanel() { - return mMoreSuggestionsView.isShowingInParent(); - } - - public void dismissMoreSuggestionsPanel() { - mMoreSuggestionsView.dismissPopupKeysPanel(); - } - - @Override - public boolean onLongClick(final View view) { - AudioAndHapticFeedbackManager.getInstance().performHapticAndAudioFeedback(Constants.NOT_A_CODE, this); - if (view.getTag() instanceof ToolbarKey) { - onLongClickToolKey(view); - return true; - } - if (view instanceof TextView && mWordViews.contains(view)) { - return onLongClickSuggestion((TextView) view); - } else return showMoreSuggestions(); - } - - private void onLongClickToolKey(final View view) { - if (!(view.getTag() instanceof ToolbarKey tag)) return; - if (view.getParent() == mPinnedKeys || !Settings.getValues().mQuickPinToolbarKeys) { - final int longClickCode = getCodeForToolbarKeyLongClick(tag); - if (longClickCode != KeyCode.UNSPECIFIED) { - mListener.onCodeInput(longClickCode, Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE, false); - } - } else if (view.getParent() == mToolbar) { - final View pinnedKeyView = mPinnedKeys.findViewWithTag(tag); - if (pinnedKeyView == null) { - addKeyToPinnedKeys(tag); - mToolbar.findViewWithTag(tag).setBackground(mEnabledToolKeyBackground); - ToolbarUtilsKt.addPinnedKey(KtxKt.prefs(getContext()), tag); - } else { - ToolbarUtilsKt.removePinnedKey(KtxKt.prefs(getContext()), tag); - mToolbar.findViewWithTag(tag).setBackground(mDefaultBackground.getConstantState().newDrawable(getResources())); - mPinnedKeys.removeView(pinnedKeyView); - } - } - } - - @SuppressLint("ClickableViewAccessibility") // no need for View#performClick, we return false mostly anyway - private boolean onLongClickSuggestion(final TextView wordView) { - boolean showIcon = true; - if (wordView.getTag() instanceof Integer) { - final int index = (int) wordView.getTag(); - if (index < mSuggestedWords.size() && mSuggestedWords.getInfo(index).mSourceDict == Dictionary.DICTIONARY_USER_TYPED) - showIcon = false; - } - if (showIcon) { - final Drawable icon = mBinIcon; - Settings.getValues().mColors.setColor(icon, ColorType.REMOVE_SUGGESTION_ICON); - int w = icon.getIntrinsicWidth(); - int h = icon.getIntrinsicWidth(); - wordView.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null); - wordView.setEllipsize(TextUtils.TruncateAt.END); - AtomicBoolean downOk = new AtomicBoolean(false); - wordView.setOnTouchListener((view1, motionEvent) -> { - if (motionEvent.getAction() == MotionEvent.ACTION_UP && downOk.get()) { - final float x = motionEvent.getX(); - final float y = motionEvent.getY(); - if (0 < x && x < w && 0 < y && y < h) { - removeSuggestion(wordView); - wordView.cancelLongPress(); - wordView.setPressed(false); - return true; - } - } else if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) { - final float x = motionEvent.getX(); - final float y = motionEvent.getY(); - if (0 < x && x < w && 0 < y && y < h) { - downOk.set(true); - } - } - return false; - }); - } - if (DebugFlags.DEBUG_ENABLED && (isShowingMoreSuggestionPanel() || !showMoreSuggestions())) { - showSourceDict(wordView); - return true; - } else return showMoreSuggestions(); - } - - private void showSourceDict(final TextView wordView) { - final String word = wordView.getText().toString(); - final int index; - if (wordView.getTag() instanceof Integer) { - index = (int) wordView.getTag(); - } else return; - if (index >= mSuggestedWords.size()) return; - final SuggestedWordInfo info = mSuggestedWords.getInfo(index); - if (!info.getWord().equals(word)) return; - final String text = info.mSourceDict.mDictType + ":" + info.mSourceDict.mLocale; - if (isShowingMoreSuggestionPanel()) { - mMoreSuggestionsView.dismissPopupKeysPanel(); - } - KeyboardSwitcher.getInstance().showToast(text, true); - } - - private void removeSuggestion(TextView wordView) { - final String word = wordView.getText().toString(); - mListener.removeSuggestion(word); - mMoreSuggestionsView.dismissPopupKeysPanel(); - // show suggestions, but without the removed word - final ArrayList sw = new ArrayList<>(); - for (int i = 0; i < mSuggestedWords.size(); i ++) { - final SuggestedWordInfo info = mSuggestedWords.getInfo(i); - if (!info.getWord().equals(word)) - sw.add(info); - } - ArrayList rs = null; - if (mSuggestedWords.mRawSuggestions != null) { - rs = mSuggestedWords.mRawSuggestions; - for (int i = 0; i < rs.size(); i ++) { - if (rs.get(i).getWord().equals(word)) { - rs.remove(i); - break; - } - } - } - // copied code from setSuggestions, but without the Rtl part - clear(); - mSuggestedWords = new SuggestedWords(sw, rs, mSuggestedWords.getTypedWordInfo(), - mSuggestedWords.mTypedWordValid, mSuggestedWords.mWillAutoCorrect, - mSuggestedWords.mIsObsoleteSuggestions, mSuggestedWords.mInputStyle, - mSuggestedWords.mSequenceNumber); - mStartIndexOfMoreSuggestions = mLayoutHelper.layoutAndReturnStartIndexOfMoreSuggestions( - getContext(), mSuggestedWords, mSuggestionsStrip, this); - mStripVisibilityGroup.showSuggestionsStrip(); - // Show the toolbar if no suggestions are left and the "Auto show toolbar" setting is enabled - if (mSuggestedWords.isEmpty() && Settings.getValues().mAutoShowToolbar){ - setToolbarVisibility(true); - } - } - - boolean showMoreSuggestions() { - final Keyboard parentKeyboard = mMainKeyboardView.getKeyboard(); - if (parentKeyboard == null) { - return false; - } - final SuggestionStripLayoutHelper layoutHelper = mLayoutHelper; - if (mSuggestedWords.size() <= mStartIndexOfMoreSuggestions) { - return false; - } - final int stripWidth = getWidth(); - final View container = mMoreSuggestionsContainer; - final int maxWidth = stripWidth - container.getPaddingLeft() - container.getPaddingRight(); - final MoreSuggestions.Builder builder = mMoreSuggestionsBuilder; - builder.layout(mSuggestedWords, mStartIndexOfMoreSuggestions, maxWidth, - (int)(maxWidth * layoutHelper.mMinMoreSuggestionsWidth), - layoutHelper.getMaxMoreSuggestionsRow(), parentKeyboard); - mMoreSuggestionsView.setKeyboard(builder.build()); - container.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - - final int pointX = stripWidth / 2; - final int pointY = -layoutHelper.mMoreSuggestionsBottomGap; - mMoreSuggestionsView.showPopupKeysPanel(this, mMoreSuggestionsController, pointX, pointY, - mMoreSuggestionsListener); - mOriginX = mLastX; - mOriginY = mLastY; - for (int i = 0; i < mStartIndexOfMoreSuggestions; i++) { - mWordViews.get(i).setPressed(false); - } - return true; - } - - // Working variables for {@link onInterceptTouchEvent(MotionEvent)} and - // {@link onTouchEvent(MotionEvent)}. - private int mLastX; - private int mLastY; - private int mOriginX; - private int mOriginY; - private final int mMoreSuggestionsModalTolerance; - private boolean mNeedsToTransformTouchEventToHoverEvent; - private boolean mIsDispatchingHoverEventToMoreSuggestions; - private final GestureDetector mMoreSuggestionsSlidingDetector; - private final GestureDetector.OnGestureListener mMoreSuggestionsSlidingListener = - new GestureDetector.SimpleOnGestureListener() { - @Override - public boolean onScroll(@Nullable MotionEvent down, @NonNull MotionEvent me, float deltaX, float deltaY) { - if (down == null) return false; - final float dy = me.getY() - down.getY(); - if (mToolbarContainer.getVisibility() != VISIBLE && deltaY > 0 && dy < 0) { - return showMoreSuggestions(); - } - return false; - } - }; - - @Override - public boolean onInterceptTouchEvent(final MotionEvent me) { - - // Disable More Suggestions if inline autofill suggestions is visible - if(isExternalSuggestionVisible) { - return false; - } - - // Detecting sliding up finger to show {@link MoreSuggestionsView}. - if (!mMoreSuggestionsView.isShowingInParent()) { - mLastX = (int)me.getX(); - mLastY = (int)me.getY(); - return mMoreSuggestionsSlidingDetector.onTouchEvent(me); - } - if (mMoreSuggestionsView.isInModalMode()) { - return false; - } - - final int action = me.getAction(); - final int index = me.getActionIndex(); - final int x = (int)me.getX(index); - final int y = (int)me.getY(index); - if (Math.abs(x - mOriginX) >= mMoreSuggestionsModalTolerance - || mOriginY - y >= mMoreSuggestionsModalTolerance) { - // Decided to be in the sliding suggestion mode only when the touch point has been moved - // upward. Further {@link MotionEvent}s will be delivered to - // {@link #onTouchEvent(MotionEvent)}. - mNeedsToTransformTouchEventToHoverEvent = - AccessibilityUtils.Companion.getInstance().isTouchExplorationEnabled(); - mIsDispatchingHoverEventToMoreSuggestions = false; - return true; - } - - if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_POINTER_UP) { - // Decided to be in the modal input mode. - mMoreSuggestionsView.setModalMode(); - } - return false; - } - - @Override - public boolean dispatchPopulateAccessibilityEvent(final AccessibilityEvent event) { - // Don't populate accessibility event with suggested words and voice key. - return true; - } - - @Override - @SuppressLint("ClickableViewAccessibility") // ok, perform click again, but why? - public boolean onTouchEvent(final MotionEvent me) { - if (!mMoreSuggestionsView.isShowingInParent()) { - // Ignore any touch event while more suggestions panel hasn't been shown. - // Detecting sliding up is done at {@link #onInterceptTouchEvent}. - return true; - } - // In the sliding input mode. {@link MotionEvent} should be forwarded to - // {@link MoreSuggestionsView}. - final int index = me.getActionIndex(); - final int x = mMoreSuggestionsView.translateX((int)me.getX(index)); - final int y = mMoreSuggestionsView.translateY((int)me.getY(index)); - me.setLocation(x, y); - if (!mNeedsToTransformTouchEventToHoverEvent) { - mMoreSuggestionsView.onTouchEvent(me); - return true; - } - // In sliding suggestion mode with accessibility mode on, a touch event should be - // transformed to a hover event. - final int width = mMoreSuggestionsView.getWidth(); - final int height = mMoreSuggestionsView.getHeight(); - final boolean onMoreSuggestions = (x >= 0 && x < width && y >= 0 && y < height); - if (!onMoreSuggestions && !mIsDispatchingHoverEventToMoreSuggestions) { - // Just drop this touch event because dispatching hover event isn't started yet and - // the touch event isn't on {@link MoreSuggestionsView}. - return true; - } - final int hoverAction; - if (onMoreSuggestions && !mIsDispatchingHoverEventToMoreSuggestions) { - // Transform this touch event to a hover enter event and start dispatching a hover - // event to {@link MoreSuggestionsView}. - mIsDispatchingHoverEventToMoreSuggestions = true; - hoverAction = MotionEvent.ACTION_HOVER_ENTER; - } else if (me.getActionMasked() == MotionEvent.ACTION_UP) { - // Transform this touch event to a hover exit event and stop dispatching a hover event - // after this. - mIsDispatchingHoverEventToMoreSuggestions = false; - mNeedsToTransformTouchEventToHoverEvent = false; - hoverAction = MotionEvent.ACTION_HOVER_EXIT; - } else { - // Transform this touch event to a hover move event. - hoverAction = MotionEvent.ACTION_HOVER_MOVE; - } - me.setAction(hoverAction); - mMoreSuggestionsView.onHoverEvent(me); - return true; - } - - @Override - public void onClick(final View view) { - AudioAndHapticFeedbackManager.getInstance().performHapticAndAudioFeedback(KeyCode.NOT_SPECIFIED, this); - final Object tag = view.getTag(); - if (tag instanceof ToolbarKey) { - final int code = getCodeForToolbarKey((ToolbarKey) tag); - if (code != KeyCode.UNSPECIFIED) { - Log.d(TAG, "click toolbar key "+tag); - mListener.onCodeInput(code, Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE, false); - if (tag == ToolbarKey.INCOGNITO) - updateKeys(); // update expand key icon - return; - } - } - if (view == mToolbarExpandKey) { - setToolbarVisibility(mToolbarContainer.getVisibility() != VISIBLE); - } - - - // {@link Integer} tag is set at - // {@link SuggestionStripLayoutHelper#setupWordViewsTextAndColor(SuggestedWords,int)} and - // {@link SuggestionStripLayoutHelper#layoutPunctuationSuggestions(SuggestedWords,ViewGroup} - if (tag instanceof Integer) { - final int index = (Integer) tag; - if (index >= mSuggestedWords.size()) { - return; - } - final SuggestedWordInfo wordInfo = mSuggestedWords.getInfo(index); - mListener.pickSuggestionManually(wordInfo); - } - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - dismissMoreSuggestionsPanel(); - } - - @Override - protected void onSizeChanged(final int w, final int h, final int oldw, final int oldh) { - // Called by the framework when the size is known. Show the important notice if applicable. - // This may be overriden by showing suggestions later, if applicable. - } - - public void setToolbarVisibility(final boolean visible) { - final KeyguardManager km = (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE); - final boolean locked = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1 - ? km.isDeviceLocked() - : km.isKeyguardLocked(); - if (locked) { - mPinnedKeys.setVisibility(GONE); - mSuggestionsStrip.setVisibility(VISIBLE); - mToolbarContainer.setVisibility(GONE); - } else if (visible) { - mPinnedKeys.setVisibility(GONE); - mSuggestionsStrip.setVisibility(GONE); - mToolbarContainer.setVisibility(VISIBLE); - } else { - mToolbarContainer.setVisibility(GONE); - mSuggestionsStrip.setVisibility(VISIBLE); - mPinnedKeys.setVisibility(VISIBLE); - } - - if (DEBUG_SUGGESTIONS) { - for (var view : mDebugInfoViews) { - view.setVisibility(mSuggestionsStrip.getVisibility()); - } - } - - mToolbarExpandKey.setScaleX((visible && !locked ? -1f : 1f) * mRtl); - } - - private void addKeyToPinnedKeys(final ToolbarKey pinnedKey) { - final ImageButton original = mToolbar.findViewWithTag(pinnedKey); - if (original == null) return; - final ImageButton copy = new ImageButton(getContext(), null, R.attr.suggestionWordStyle); - copy.setTag(pinnedKey); - copy.setScaleType(original.getScaleType()); - copy.setScaleX(original.getScaleX()); - copy.setScaleY(original.getScaleY()); - copy.setContentDescription(original.getContentDescription()); - copy.setImageDrawable(original.getDrawable()); - copy.setLayoutParams(original.getLayoutParams()); - copy.setActivated(original.isActivated()); - setupKey(copy, Settings.getValues().mColors); - mPinnedKeys.addView(copy); - } - - private void setupKey(final ImageButton view, final Colors colors) { - view.setOnClickListener(this); - view.setOnLongClickListener(this); - colors.setColor(view, ColorType.TOOL_BAR_KEY); - colors.setBackground(view, ColorType.STRIP_BACKGROUND); - } -} diff --git a/app/src/main/java/helium314/keyboard/latin/suggestions/SuggestionStripView.kt b/app/src/main/java/helium314/keyboard/latin/suggestions/SuggestionStripView.kt new file mode 100644 index 000000000..c37577caf --- /dev/null +++ b/app/src/main/java/helium314/keyboard/latin/suggestions/SuggestionStripView.kt @@ -0,0 +1,651 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * modified + * SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only + */ +package helium314.keyboard.latin.suggestions + +import android.annotation.SuppressLint +import android.app.KeyguardManager +import android.content.Context +import android.content.SharedPreferences +import android.content.SharedPreferences.OnSharedPreferenceChangeListener +import android.graphics.Color +import android.graphics.drawable.Drawable +import android.graphics.drawable.GradientDrawable +import android.os.Build +import android.text.TextUtils +import android.util.AttributeSet +import android.util.TypedValue +import android.view.GestureDetector +import android.view.GestureDetector.SimpleOnGestureListener +import android.view.LayoutInflater +import android.view.MotionEvent +import android.view.View +import android.view.View.OnLongClickListener +import android.view.ViewGroup +import android.view.accessibility.AccessibilityEvent +import android.widget.ImageButton +import android.widget.LinearLayout +import android.widget.RelativeLayout +import android.widget.TextView +import androidx.core.view.isVisible +import helium314.keyboard.accessibility.AccessibilityUtils +import helium314.keyboard.keyboard.KeyboardSwitcher +import helium314.keyboard.keyboard.MainKeyboardView +import helium314.keyboard.keyboard.PopupKeysPanel +import helium314.keyboard.keyboard.internal.KeyboardIconsSet +import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode +import helium314.keyboard.latin.AudioAndHapticFeedbackManager +import helium314.keyboard.latin.Dictionary +import helium314.keyboard.latin.R +import helium314.keyboard.latin.SuggestedWords +import helium314.keyboard.latin.SuggestedWords.SuggestedWordInfo +import helium314.keyboard.latin.common.ColorType +import helium314.keyboard.latin.common.Colors +import helium314.keyboard.latin.common.Constants +import helium314.keyboard.latin.define.DebugFlags +import helium314.keyboard.latin.settings.DebugSettings +import helium314.keyboard.latin.settings.Defaults +import helium314.keyboard.latin.settings.Settings +import helium314.keyboard.latin.suggestions.PopupSuggestionsView.MoreSuggestionsListener +import helium314.keyboard.latin.utils.Log +import helium314.keyboard.latin.utils.ToolbarKey +import helium314.keyboard.latin.utils.ToolbarMode +import helium314.keyboard.latin.utils.addPinnedKey +import helium314.keyboard.latin.utils.createToolbarKey +import helium314.keyboard.latin.utils.getCodeForToolbarKey +import helium314.keyboard.latin.utils.getCodeForToolbarKeyLongClick +import helium314.keyboard.latin.utils.getEnabledToolbarKeys +import helium314.keyboard.latin.utils.getPinnedToolbarKeys +import helium314.keyboard.latin.utils.prefs +import helium314.keyboard.latin.utils.removeFirst +import helium314.keyboard.latin.utils.removePinnedKey +import helium314.keyboard.latin.utils.setToolbarButtonsActivatedStateOnPrefChange +import java.util.concurrent.atomic.AtomicBoolean +import kotlin.math.abs +import kotlin.math.min + +class SuggestionStripView(context: Context, attrs: AttributeSet?, defStyle: Int) : + RelativeLayout(context, attrs, defStyle), View.OnClickListener, OnLongClickListener, OnSharedPreferenceChangeListener { + + /** Construct a [SuggestionStripView] for showing suggestions to be picked by the user. */ + constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, R.attr.suggestionStripViewStyle) + + interface Listener { + fun pickSuggestionManually(word: SuggestedWordInfo?) + fun onCodeInput(primaryCode: Int, x: Int, y: Int, isKeyRepeat: Boolean) + fun removeSuggestion(word: String?) + } + + private val moreSuggestionsContainer: View + private val wordViews = ArrayList() + private val debugInfoViews = ArrayList() + private val dividerViews = ArrayList() + + init { + val inflater = LayoutInflater.from(context) + inflater.inflate(R.layout.suggestions_strip, this) + moreSuggestionsContainer = inflater.inflate(R.layout.more_suggestions, null) + + val colors = Settings.getValues().mColors + colors.setBackground(this, ColorType.STRIP_BACKGROUND) + val customTypeface = Settings.getInstance().customTypeface + for (pos in 0..(R.id.suggestions_strip_toolbar_key) + private val incognitoIcon = KeyboardIconsSet.instance.getNewDrawable(ToolbarKey.INCOGNITO.name, context) + private val toolbarArrowIcon = KeyboardIconsSet.instance.getNewDrawable(KeyboardIconsSet.NAME_TOOLBAR_KEY, context) + private val defaultToolbarBackground: Drawable = toolbarExpandKey.background + private val enabledToolKeyBackground = GradientDrawable() + init { + val colors = Settings.getValues().mColors + + // expand key + val toolbarHeight = min(toolbarExpandKey.layoutParams.height, resources.getDimension(R.dimen.config_suggestions_strip_height).toInt()) + toolbarExpandKey.layoutParams.height = toolbarHeight + toolbarExpandKey.layoutParams.width = toolbarHeight // we want it square + colors.setBackground(toolbarExpandKey, ColorType.STRIP_BACKGROUND) // necessary because background is re-used for defaultToolbarBackground + colors.setColor(toolbarExpandKey, ColorType.TOOL_BAR_EXPAND_KEY) + colors.setColor(toolbarExpandKey.background, ColorType.TOOL_BAR_EXPAND_KEY_BACKGROUND) + + // background indicator for pinned keys + val color = colors.get(ColorType.TOOL_BAR_KEY_ENABLED_BACKGROUND) or -0x1000000 // ignore alpha (in Java this is more readable 0xFF000000) + enabledToolKeyBackground.colors = intArrayOf(color, Color.TRANSPARENT) + enabledToolKeyBackground.gradientType = GradientDrawable.RADIAL_GRADIENT + enabledToolKeyBackground.gradientRadius = resources.getDimensionPixelSize(R.dimen.config_suggestions_strip_height) / 2.1f + + val mToolbarMode = Settings.getValues().mToolbarMode + if (mToolbarMode == ToolbarMode.TOOLBAR_KEYS) { + setToolbarVisibility(true) + } + + // toolbar keys setup + val toolbarKeyLayoutParams = LinearLayout.LayoutParams( + resources.getDimensionPixelSize(R.dimen.config_suggestions_strip_edge_key_width), + LinearLayout.LayoutParams.MATCH_PARENT + ) + if (mToolbarMode == ToolbarMode.TOOLBAR_KEYS || mToolbarMode == ToolbarMode.EXPANDABLE) { + for (key in getEnabledToolbarKeys(context.prefs())) { + val button = createToolbarKey(context, KeyboardIconsSet.instance, key) + button.layoutParams = toolbarKeyLayoutParams + setupKey(button, colors) + toolbar.addView(button) + } + } + if (!Settings.getValues().mSuggestionStripHiddenPerUserSettings) { + for (pinnedKey in getPinnedToolbarKeys(context.prefs())) { + val button = createToolbarKey(context, KeyboardIconsSet.instance, pinnedKey) + button.layoutParams = toolbarKeyLayoutParams + setupKey(button, colors) + pinnedKeys.addView(button) + val pinnedKeyInToolbar = toolbar.findViewWithTag(pinnedKey) + if (pinnedKeyInToolbar != null && Settings.getValues().mQuickPinToolbarKeys) + pinnedKeyInToolbar.background = enabledToolKeyBackground + } + } + + updateKeys() + } + + private val layoutHelper = SuggestionStripLayoutHelper(context, attrs, defStyle, wordViews, dividerViews, debugInfoViews) + private lateinit var listener: Listener + private lateinit var mainKeyboardView: MainKeyboardView + private var suggestedWords = SuggestedWords.getEmptyInstance() + private var startIndexOfMoreSuggestions = 0 + private var direction = 1 // 1 if LTR, -1 if RTL + private var isExternalSuggestionVisible = false // Required to disable the more suggestions if other suggestions are visible + + // related to more suggestions + // todo: maybe put most of this in a separate class? + private val moreSuggestionsView: PopupSuggestionsView = moreSuggestionsContainer.findViewById(R.id.more_suggestions_view) + private val moreSuggestionsBuilder = MoreSuggestions.Builder(context, moreSuggestionsView) // todo: why actually here? + private val moreSuggestionsModalTolerance = context.resources.getDimensionPixelOffset(R.dimen.config_more_suggestions_modal_tolerance) + private val moreSuggestionsListener = object : MoreSuggestionsListener() { + override fun onSuggestionSelected(wordInfo: SuggestedWordInfo) { + listener.pickSuggestionManually(wordInfo) + dismissMoreSuggestionsPanel() + } + + override fun onCancelInput() { + dismissMoreSuggestionsPanel() + } + } + private val moreSuggestionsSlidingListener = object : SimpleOnGestureListener() { + override fun onScroll(down: MotionEvent?, me: MotionEvent, deltaX: Float, deltaY: Float): Boolean { + if (down == null) return false + val dy = me.y - down.y + return if (toolbarContainer.visibility != VISIBLE && deltaY > 0 && dy < 0) showMoreSuggestions() + else false + } + } + private val moreSuggestionsSlidingDetector = GestureDetector(context, moreSuggestionsSlidingListener) + // Working variables for onInterceptTouchEvent(MotionEvent) and onTouchEvent(MotionEvent). + private var lastX = 0 + private var lastY = 0 + private var originX = 0 + private var originY = 0 + private var needsToTransformTouchEventToHoverEvent = false + private var isDispatchingHoverEventToMoreSuggestions = false + private val moreSuggestionsController: PopupKeysPanel.Controller = object : PopupKeysPanel.Controller { + override fun onDismissPopupKeysPanel() { + mainKeyboardView.onDismissPopupKeysPanel() + } + + override fun onShowPopupKeysPanel(panel: PopupKeysPanel) { + mainKeyboardView.onShowPopupKeysPanel(panel) + } + + override fun onCancelPopupKeysPanel() { + dismissMoreSuggestionsPanel() + } + } + + // public stuff + + val isShowingMoreSuggestionPanel get() = moreSuggestionsView.isShowingInParent + + /** A connection back to the input method. */ + fun setListener(newListener: Listener, inputView: View) { + listener = newListener + mainKeyboardView = inputView.findViewById(R.id.keyboard_view) + } + + fun setRtl(isRtlLanguage: Boolean) { + val newLayoutDirection: Int + if (!Settings.getValues().mVarToolbarDirection) + newLayoutDirection = LAYOUT_DIRECTION_LOCALE + else { + newLayoutDirection = if (isRtlLanguage) LAYOUT_DIRECTION_RTL else LAYOUT_DIRECTION_LTR + direction = if (isRtlLanguage) -1 else 1 + toolbarExpandKey.scaleX = (if (toolbarContainer.visibility != VISIBLE) 1f else -1f) * direction + } + layoutDirection = newLayoutDirection + suggestionsStrip.layoutDirection = newLayoutDirection + } + + fun setToolbarVisibility(toolbarVisible: Boolean) { + // avoid showing toolbar keys when locked + val km = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager + val locked = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) // todo: move this check to compat (also other uses) + km.isDeviceLocked + else + km.isKeyguardLocked + pinnedKeys.isVisible = !locked && !toolbarVisible + suggestionsStrip.isVisible = locked || !toolbarVisible + toolbarContainer.isVisible = !locked && toolbarVisible + + if (DEBUG_SUGGESTIONS) { + for (view in debugInfoViews) { + view.visibility = suggestionsStrip.visibility + } + } + + toolbarExpandKey.scaleX = (if (toolbarVisible && !locked) -1f else 1f) * direction + } + + fun setSuggestions(suggestions: SuggestedWords, isRtlLanguage: Boolean) { + clear() + setRtl(isRtlLanguage) + suggestedWords = suggestions + startIndexOfMoreSuggestions = layoutHelper.layoutAndReturnStartIndexOfMoreSuggestions( + context, suggestedWords, suggestionsStrip, this + ) + isExternalSuggestionVisible = false + } + + fun setExternalSuggestionView(view: View?) { + clear() + isExternalSuggestionVisible = true + suggestionsStrip.addView(view) + if (Settings.getValues().mAutoHideToolbar) setToolbarVisibility(false) + } + + fun setMoreSuggestionsHeight(remainingHeight: Int) { + layoutHelper.setMoreSuggestionsHeight(remainingHeight) + } + + fun dismissMoreSuggestionsPanel() { + moreSuggestionsView.dismissPopupKeysPanel() + } + + // overrides: necessarily public, but not used from outside + + override fun onSharedPreferenceChanged(prefs: SharedPreferences, key: String?) { + setToolbarButtonsActivatedStateOnPrefChange(pinnedKeys, key) + setToolbarButtonsActivatedStateOnPrefChange(toolbar, key) + } + + override fun onVisibilityChanged(view: View, visibility: Int) { + super.onVisibilityChanged(view, visibility) + // workaround for a bug with inline suggestions views that just keep showing up otherwise, https://github.com/Helium314/HeliBoard/pull/386 + if (view === this) + suggestionsStrip.visibility = visibility + } + + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + dismissMoreSuggestionsPanel() + } + + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + // Called by the framework when the size is known. Show the important notice if applicable. + // This may be overridden by showing suggestions later, if applicable. + } + + override fun dispatchPopulateAccessibilityEvent(event: AccessibilityEvent): Boolean { + // Don't populate accessibility event with suggested words and voice key. + return true + } + + // this is only for moreSuggestionsView + override fun onInterceptTouchEvent(motionEvent: MotionEvent): Boolean { + // Disable More Suggestions if external suggestions are visible + if (isExternalSuggestionVisible) { + return false + } + + // Detecting sliding up finger to show MoreSuggestionsView. + if (!moreSuggestionsView.isShowingInParent) { + lastX = motionEvent.x.toInt() + lastY = motionEvent.y.toInt() + return moreSuggestionsSlidingDetector.onTouchEvent(motionEvent) + } + if (moreSuggestionsView.isInModalMode) { + return false + } + + val index = motionEvent.actionIndex + if (abs((motionEvent.getX(index).toInt() - originX).toDouble()) >= moreSuggestionsModalTolerance + || originY - motionEvent.getY(index).toInt() >= moreSuggestionsModalTolerance + ) { + // Decided to be in the sliding suggestion mode only when the touch point has been moved + // upward. Further MotionEvents will be delivered to onTouchEvent(MotionEvent). + needsToTransformTouchEventToHoverEvent = AccessibilityUtils.instance.isTouchExplorationEnabled + isDispatchingHoverEventToMoreSuggestions = false + return true + } + + if (motionEvent.action == MotionEvent.ACTION_UP || motionEvent.action == MotionEvent.ACTION_POINTER_UP) { + // Decided to be in the modal input mode. + moreSuggestionsView.setModalMode() + } + return false + } + + // this is only for moreSuggestionsView + @SuppressLint("ClickableViewAccessibility") + override fun onTouchEvent(motionEvent: MotionEvent): Boolean { + if (!moreSuggestionsView.isShowingInParent) { + // Ignore any touch event while more suggestions panel hasn't been shown. + return true + } + // In the sliding input mode. {@link MotionEvent} should be forwarded to + // {@link MoreSuggestionsView}. + val index = motionEvent.actionIndex + val x = moreSuggestionsView.translateX(motionEvent.getX(index).toInt()) + val y = moreSuggestionsView.translateY(motionEvent.getY(index).toInt()) + motionEvent.setLocation(x.toFloat(), y.toFloat()) + if (!needsToTransformTouchEventToHoverEvent) { + moreSuggestionsView.onTouchEvent(motionEvent) + return true + } + // In sliding suggestion mode with accessibility mode on, a touch event should be + // transformed to a hover event. + val width = moreSuggestionsView.width + val height = moreSuggestionsView.height + val onMoreSuggestions = x in 0..= suggestedWords.size()) { + return + } + val wordInfo = suggestedWords.getInfo(tag) + listener.pickSuggestionManually(wordInfo) + } + } + + override fun onLongClick(view: View): Boolean { + AudioAndHapticFeedbackManager.getInstance().performHapticAndAudioFeedback(Constants.NOT_A_CODE, this) + if (view.tag is ToolbarKey) { + onLongClickToolbarKey(view) + return true + } + return if (view is TextView && wordViews.contains(view)) { + onLongClickSuggestion(view) + } else { + showMoreSuggestions() + } + } + + // actually private stuff + + private fun onLongClickToolbarKey(view: View) { + val tag = view.tag as? ToolbarKey ?: return + if (!Settings.getValues().mQuickPinToolbarKeys || view.parent === pinnedKeys) { + val longClickCode = getCodeForToolbarKeyLongClick(tag) + if (longClickCode != KeyCode.UNSPECIFIED) { + listener.onCodeInput(longClickCode, Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE, false) + } + } else if (view.parent === toolbar) { + val pinnedKeyView = pinnedKeys.findViewWithTag(tag) + if (pinnedKeyView == null) { + addKeyToPinnedKeys(tag) + toolbar.findViewWithTag(tag).background = enabledToolKeyBackground + addPinnedKey(context.prefs(), tag) + } else { + removePinnedKey(context.prefs(), tag) + toolbar.findViewWithTag(tag).background = defaultToolbarBackground.constantState?.newDrawable(resources) + pinnedKeys.removeView(pinnedKeyView) + } + } + } + + @SuppressLint("ClickableViewAccessibility") // no need for View#performClick, we only return false mostly anyway + private fun onLongClickSuggestion(wordView: TextView): Boolean { + var showIcon = true + if (wordView.tag is Int) { + val index = wordView.tag as Int + if (index < suggestedWords.size() && suggestedWords.getInfo(index).mSourceDict == Dictionary.DICTIONARY_USER_TYPED) + showIcon = false + } + if (showIcon) { + val icon = KeyboardIconsSet.instance.getNewDrawable(KeyboardIconsSet.NAME_BIN, context)!! + Settings.getValues().mColors.setColor(icon, ColorType.REMOVE_SUGGESTION_ICON) + val w = icon.intrinsicWidth + val h = icon.intrinsicHeight + wordView.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null) + wordView.ellipsize = TextUtils.TruncateAt.END + val downOk = AtomicBoolean(false) + wordView.setOnTouchListener { _, motionEvent -> + if (motionEvent.action == MotionEvent.ACTION_UP && downOk.get()) { + val x = motionEvent.x + val y = motionEvent.y + if (0 < x && x < w && 0 < y && y < h) { + removeSuggestion(wordView) + wordView.cancelLongPress() + wordView.isPressed = false + return@setOnTouchListener true + } + } else if (motionEvent.action == MotionEvent.ACTION_DOWN) { + val x = motionEvent.x + val y = motionEvent.y + if (0 < x && x < w && 0 < y && y < h) { + downOk.set(true) + } + } + false + } + } + if (DebugFlags.DEBUG_ENABLED && (isShowingMoreSuggestionPanel || !showMoreSuggestions())) { + showSourceDict(wordView) + return true + } + return showMoreSuggestions() + } + + private fun showMoreSuggestions(): Boolean { + val parentKeyboard = mainKeyboardView.keyboard ?: return false + if (suggestedWords.size() <= startIndexOfMoreSuggestions) { + return false + } + val container = moreSuggestionsContainer + val maxWidth = width - container.paddingLeft - container.paddingRight + val keyboard = moreSuggestionsBuilder.layout( + suggestedWords, startIndexOfMoreSuggestions, maxWidth, + (maxWidth * layoutHelper.mMinMoreSuggestionsWidth).toInt(), + layoutHelper.maxMoreSuggestionsRow, parentKeyboard + ).build() + moreSuggestionsView.setKeyboard(keyboard) + container.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT) + + val pointX = width / 2 + val pointY = -layoutHelper.mMoreSuggestionsBottomGap + moreSuggestionsView.showPopupKeysPanel(this, moreSuggestionsController, pointX, pointY, moreSuggestionsListener) + originX = lastX + originY = lastY + for (i in 0..= suggestedWords.size()) return + val info = suggestedWords.getInfo(index) + if (info.word != word) return + + val text = info.mSourceDict.mDictType + ":" + info.mSourceDict.mLocale + if (isShowingMoreSuggestionPanel) { + moreSuggestionsView.dismissPopupKeysPanel() + } + KeyboardSwitcher.getInstance().showToast(text, true) + } + + private fun removeSuggestion(wordView: TextView) { + val word = wordView.text.toString() + listener.removeSuggestion(word) + moreSuggestionsView.dismissPopupKeysPanel() + // show suggestions, but without the removed word + val suggestedWordInfos = ArrayList() + for (i in 0..(ToolbarKey.VOICE) + if (toolbarVoiceKey != null) toolbarVoiceKey.isVisible = settingsValues.mShowsVoiceInputKey + val pinnedVoiceKey = pinnedKeys.findViewWithTag(ToolbarKey.VOICE) + if (pinnedVoiceKey != null) pinnedVoiceKey.isVisible = settingsValues.mShowsVoiceInputKey + + val toolbarIsExpandable = settingsValues.mToolbarMode == ToolbarMode.EXPANDABLE + if (settingsValues.mIncognitoModeEnabled) { + toolbarExpandKey.setImageDrawable(incognitoIcon) + toolbarExpandKey.isVisible = true + } else { + toolbarExpandKey.setImageDrawable(toolbarArrowIcon) + toolbarExpandKey.isVisible = toolbarIsExpandable + } + + // hide pinned keys if device is locked, and avoid expanding toolbar + val km = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager + val hideToolbarKeys = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) + km.isDeviceLocked + else + km.isKeyguardLocked + toolbarExpandKey.setOnClickListener(if (hideToolbarKeys || !toolbarIsExpandable) null else this) + pinnedKeys.visibility = if (hideToolbarKeys) GONE else suggestionsStrip.visibility + isExternalSuggestionVisible = false + } + + private fun addKeyToPinnedKeys(pinnedKey: ToolbarKey) { + val original = toolbar.findViewWithTag(pinnedKey) ?: return + // copy the original key to a new ImageButton + val copy = ImageButton(context, null, R.attr.suggestionWordStyle) + copy.tag = pinnedKey + copy.scaleType = original.scaleType + copy.scaleX = original.scaleX + copy.scaleY = original.scaleY + copy.contentDescription = original.contentDescription + copy.setImageDrawable(original.drawable) + copy.layoutParams = original.layoutParams + copy.isActivated = original.isActivated + setupKey(copy, Settings.getValues().mColors) + pinnedKeys.addView(copy) + } + + private fun setupKey(view: ImageButton, colors: Colors) { + view.setOnClickListener(this) + view.setOnLongClickListener(this) + colors.setColor(view, ColorType.TOOL_BAR_KEY) + colors.setBackground(view, ColorType.STRIP_BACKGROUND) + } + + companion object { + @JvmField + var DEBUG_SUGGESTIONS = false + private const val DEBUG_INFO_TEXT_SIZE_IN_DIP = 6.5f + private val TAG = SuggestionStripView::class.java.simpleName + } +}